diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/views | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/views')
122 files changed, 37679 insertions, 0 deletions
diff --git a/chrome/views/SConscript b/chrome/views/SConscript new file mode 100644 index 0000000..4364ba9 --- /dev/null +++ b/chrome/views/SConscript @@ -0,0 +1,124 @@ +# 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.
+
+Import('env')
+
+env = env.Clone()
+
+
+
+env.Prepend(
+ CPPPATH = [
+ env.Dir("#/../chrome/tools/build/win"),
+ env.Dir("#/.."),
+ ],
+ CPPDEFINES = [
+ "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+ "WIN32_LEAN_AND_MEAN",
+ ],
+ CCFLAGS = [
+ '/TP',
+
+ #'/Wp64',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+)
+
+env.Append(
+ CPPPATH = [
+ '../app/resources',
+ '$SKIA_DIR/include',
+ '$SKIA_DIR/include/corecg',
+ '$SKIA_DIR/include/platform',
+ 'third_party/wtl/include',
+ ],
+)
+
+input_files = [
+ 'accelerator.cc',
+ 'accelerator_handler.cc',
+ 'accessibility/accessible_wrapper.cc',
+ 'accessibility/autocomplete_accessibility.cc',
+ 'accessibility/view_accessibility.cc',
+ 'aero_tooltip_manager.cc',
+ 'background.cc',
+ 'base_button.cc',
+ 'bitmap_scroll_bar.cc',
+ 'border.cc',
+ 'button.cc',
+ 'button_dropdown.cc',
+ 'checkbox.cc',
+ 'chrome_menu.cc',
+ 'client_view.cc',
+ 'combo_box.cc',
+ 'custom_frame_window.cc',
+ 'decision.cc',
+ 'event.cc',
+ 'external_focus_tracker.cc',
+ 'focus_manager.cc',
+ # This was in the Visual Studio build, but seems unnecessary.
+ #'focus_manager_unittest.cc',
+ 'grid_layout.cc',
+ 'group_table_view.cc',
+ 'hwnd_view.cc',
+ 'hwnd_view_container.cc',
+ 'image_view.cc',
+ 'label.cc',
+ 'layout_manager.cc',
+ 'link.cc',
+ 'menu.cc',
+ 'menu_button.cc',
+ 'message_box_view.cc',
+ 'native_button.cc',
+ 'native_control.cc',
+ 'native_scroll_bar.cc',
+ 'painter.cc',
+ 'radio_button.cc',
+ 'repeat_controller.cc',
+ 'resize_corner.cc',
+ 'root_view.cc',
+ 'root_view_drop_target.cc',
+ 'scroll_bar.cc',
+ 'scroll_view.cc',
+ 'separator.cc',
+ 'tabbed_pane.cc',
+ 'table_view.cc',
+ 'text_button.cc',
+ 'text_field.cc',
+ 'throbber.cc',
+ 'tooltip_manager.cc',
+ 'tree_view.cc',
+ 'view.cc',
+ 'view_storage.cc',
+ 'window.cc',
+]
+
+env.StaticLibrary('views', input_files)
diff --git a/chrome/views/accelerator.cc b/chrome/views/accelerator.cc new file mode 100644 index 0000000..e8152ce --- /dev/null +++ b/chrome/views/accelerator.cc @@ -0,0 +1,141 @@ +// 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 <string> +#include <windows.h> + +#include "chrome/views/accelerator.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "chrome/common/l10n_util.h" +#include "generated_resources.h" + +namespace ChromeViews { + +std::wstring Accelerator::GetShortcutText() const { + int string_id = 0; + switch(key_code_) { + case VK_TAB: + string_id = IDS_TAB_KEY; + break; + case VK_RETURN: + string_id = IDS_ENTER_KEY; + break; + case VK_ESCAPE: + string_id = IDS_ESC_KEY; + break; + case VK_PRIOR: + string_id = IDS_PAGEUP_KEY; + break; + case VK_NEXT: + string_id = IDS_PAGEDOWN_KEY; + break; + case VK_END: + string_id = IDS_END_KEY; + break; + case VK_HOME: + string_id = IDS_HOME_KEY; + break; + case VK_INSERT: + string_id = IDS_INSERT_KEY; + break; + case VK_DELETE: + string_id = IDS_DELETE_KEY; + break; + } + + std::wstring shortcut; + if (!string_id) { + // Our fallback is to try translate the key code to a regular char. + wchar_t key = LOWORD(::MapVirtualKeyW(key_code_, MAPVK_VK_TO_CHAR)); + shortcut += key; + } else { + shortcut = l10n_util::GetString(string_id); + } + + // Checking whether the character used for the accelerator is alphanumeric. + // If it is not, then we need to adjust the string later on if the locale is + // right-to-left. See below for more information of why such adjustment is + // required. + std::wstring shortcut_rtl; + bool adjust_shortcut_for_rtl = false; + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT && + shortcut.length() == 1 && + !IsAsciiAlpha(shortcut.at(0)) && + !IsAsciiDigit(shortcut.at(0))) { + adjust_shortcut_for_rtl = true; + shortcut_rtl.assign(shortcut); + } + + if (IsShiftDown()) + shortcut = l10n_util::GetStringF(IDS_SHIFT_MODIFIER, shortcut); + + // Note that we use 'else-if' in order to avoid using Ctrl+Alt as a shortcut. + // See http://blogs.msdn.com/oldnewthing/archive/2004/03/29/101121.aspx for + // more information. + if (IsCtrlDown()) + shortcut = l10n_util::GetStringF(IDS_CONTROL_MODIFIER, shortcut); + else if (IsAltDown()) + shortcut = l10n_util::GetStringF(IDS_ALT_MODIFIER, shortcut); + + // For some reason, menus in Windows ignore standard Unicode directionality + // marks (such as LRE, PDF, etc.). On RTL locales, we use RTL menus and + // therefore any text we draw for the menu items is drawn in an RTL context. + // Thus, the text "Ctrl++" (which we currently use for the Zoom In option) + // appears as "++Ctrl" in RTL because the Unicode BiDi algorithm puts + // punctuations on the left when the context is right-to-left. Shortcuts that + // do not end with a punctuation mark (such as "Ctrl+H" do not have this + // problem). + // + // The only way to solve this problem is to adjust the string if the locale + // is RTL so that it is drawn correnctly in an RTL context. Instead of + // returning "Ctrl++" in the above example, we return "++Ctrl". This will + // cause the text to appear as "Ctrl++" when Windows draws the string in an + // RTL context because the punctunation no longer appears at the end of the + // string. + // + // TODO(idana) bug# 1232732: this hack can be avoided if instead of using + // ChromeViews::Menu we use ChromeViews::MenuItemView because the latter is a + // View subclass and therefore it supports marking text as RTL or LTR using + // standard Unicode directionality marks. + if (adjust_shortcut_for_rtl) { + int key_length = static_cast<int>(shortcut_rtl.length()); + DCHECK_GT(key_length, 0); + shortcut_rtl.append(L"+"); + + // Subtracting the size of the shortcut key and 1 for the '+' sign. + shortcut_rtl.append(shortcut, 0, shortcut.length() - key_length - 1); + shortcut.swap(shortcut_rtl); + } + + return shortcut; +} + +} // namespace ChromeViews diff --git a/chrome/views/accelerator.h b/chrome/views/accelerator.h new file mode 100644 index 0000000..468e0d2 --- /dev/null +++ b/chrome/views/accelerator.h @@ -0,0 +1,120 @@ +// 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. + +// This class describe a keyboard accelerator (or keyboard shortcut). +// Keyboard accelerators are registered with the FocusManager. +// It has a copy constructor and assignment operator so that it can be copied. +// It also defines the < operator so that it can be used as a key in a std::map. +// + +#ifndef CHROME_VIEWS_ACCELERATOR_H__ +#define CHROME_VIEWS_ACCELERATOR_H__ + +#include "chrome/views/event.h" + +namespace ChromeViews { + +class Accelerator { + public: + Accelerator(int keycode, + bool shift_pressed, bool ctrl_pressed, bool alt_pressed) + : key_code_(keycode) { + modifiers_ = 0; + if (shift_pressed) + modifiers_ |= Event::EF_SHIFT_DOWN; + if (ctrl_pressed) + modifiers_ |= Event::EF_CONTROL_DOWN; + if (alt_pressed) + modifiers_ |= Event::EF_ALT_DOWN; + } + + Accelerator(const Accelerator& accelerator) { + key_code_ = accelerator.key_code_; + modifiers_ = accelerator.modifiers_; + } + + ~Accelerator() { }; + + Accelerator& Accelerator::operator=(const Accelerator& accelerator) { + if (this != &accelerator) { + key_code_ = accelerator.key_code_; + modifiers_ = accelerator.modifiers_; + } + return *this; + } + + // We define the < operator so that the KeyboardShortcut can be used as a key + // in a std::map. + bool operator <(const Accelerator& rhs) const { + if (key_code_ != rhs.key_code_) + return key_code_ < rhs.key_code_; + return modifiers_ < rhs.modifiers_; + } + + bool operator ==(const Accelerator& rhs) const { + return (key_code_ == rhs.key_code_) && (modifiers_ == rhs.modifiers_); + } + + bool IsShiftDown() const { + return (modifiers_ & Event::EF_SHIFT_DOWN) == Event::EF_SHIFT_DOWN; + } + + bool IsCtrlDown() const { + return (modifiers_ & Event::EF_CONTROL_DOWN) == Event::EF_CONTROL_DOWN; + } + + bool IsAltDown() const { + return (modifiers_ & Event::EF_ALT_DOWN) == Event::EF_ALT_DOWN; + } + + int GetKeyCode() const { + return key_code_; + } + + // Returns a string with the localized shortcut if any. + std::wstring GetShortcutText() const; + + private: + // The window keycode (VK_...). + int key_code_; + + // The state of the Shift/Ctrl/Alt keys (see event.h). + int modifiers_; +}; + +// An interface that classes that want to register for keyboard accelerators +// should implement. +class AcceleratorTarget { + public: + // This method should return true if the accelerator was processed. + virtual bool AcceleratorPressed(const Accelerator& accelerator) = 0; +}; +} + +#endif // #ifndef CHROME_VIEWS_ACCELERATOR_H__
\ No newline at end of file diff --git a/chrome/views/accelerator_handler.cc b/chrome/views/accelerator_handler.cc new file mode 100644 index 0000000..6b6b341 --- /dev/null +++ b/chrome/views/accelerator_handler.cc @@ -0,0 +1,61 @@ +// 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 "chrome/views/accelerator_handler.h" + +#include "chrome/views/focus_manager.h" + +namespace ChromeViews { + +AcceleratorHandler::AcceleratorHandler() { +} + +bool AcceleratorHandler::Dispatch(const MSG& msg) { + bool process_message = true; + if ((msg.message == WM_KEYDOWN) || (msg.message == WM_SYSKEYDOWN)) { + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(msg.hwnd); + if (focus_manager) { + // FocusManager::OnKeyDown returns false if this message has been + // consumed and should not be propogated further + if (!focus_manager->OnKeyDown(msg.hwnd, msg.message, msg.wParam, + msg.lParam)) { + process_message = false; + } + } + } + if (process_message) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return true; +} + +}
\ No newline at end of file diff --git a/chrome/views/accelerator_handler.h b/chrome/views/accelerator_handler.h new file mode 100644 index 0000000..577b105 --- /dev/null +++ b/chrome/views/accelerator_handler.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef CHROME_VIEWS_ACCELERATOR_HANDLER_H__ +#define CHROME_VIEWS_ACCELERATOR_HANDLER_H__ + +#include "base/message_loop.h" + +namespace ChromeViews { + +// This class delegates WM_KEYDOWN and WM_SYSKEYDOWN messages to +// the associated FocusManager class for the window that is receiving +// these messages for accelerator processing. The BrowserProcess object +// holds a singleton instance of this class which can be used by other +// custom message loop dispatcher objects to implement default accelerator +// handling. +class AcceleratorHandler : public MessageLoop::Dispatcher { + public: + AcceleratorHandler(); + // Dispatcher method. This returns true if an accelerator was + // processed by the focus manager + virtual bool Dispatch(const MSG& msg); + private: + DISALLOW_EVIL_CONSTRUCTORS(AcceleratorHandler); +}; +} +#endif // CHROME_VIEWS_ACCELERATOR_HANDLER_H__ diff --git a/chrome/views/accessibility/accessible_wrapper.cc b/chrome/views/accessibility/accessible_wrapper.cc new file mode 100644 index 0000000..481e6b2 --- /dev/null +++ b/chrome/views/accessibility/accessible_wrapper.cc @@ -0,0 +1,105 @@ +// 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 "chrome/views/accessibility/accessible_wrapper.h" + +#include "base/logging.h" +#include "chrome/views/accessibility/view_accessibility.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// AccessibleWrapper - constructors, destructors +// +//////////////////////////////////////////////////////////////////////////////// + +AccessibleWrapper::AccessibleWrapper(ChromeViews::View* view) : + accessibility_info_(NULL), + view_(view) { +} + +STDMETHODIMP AccessibleWrapper::CreateDefaultInstance(REFIID iid) { + if (IID_IUnknown == iid || IID_IDispatch == iid || IID_IAccessible == iid) { + // If there is no instance of ViewAccessibility created, create it + // now. Otherwise reuse previous instance. + if (!accessibility_info_) { + CComObject<ViewAccessibility>* instance = NULL; + + HRESULT hr = CComObject<ViewAccessibility>::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + + if (!instance) + return E_FAIL; + + CComPtr<IAccessible> accessibility_instance(instance); + + if (!SUCCEEDED(instance->Initialize(view_))) + return E_FAIL; + + // All is well, assign the temp instance to the class smart pointer. + accessibility_info_.Attach(accessibility_instance.Detach()); + } + return S_OK; + } + // Interface not supported. + return E_NOINTERFACE; +} + +STDMETHODIMP AccessibleWrapper::GetInstance(REFIID iid, void** interface_ptr) { + if (IID_IUnknown == iid || IID_IDispatch == iid || IID_IAccessible == iid) { + // If there is no accessibility instance created, create a default now. + // Otherwise reuse previous instance. + if (!accessibility_info_) { + HRESULT hr = CreateDefaultInstance(iid); + + if (hr != S_OK) { + // Interface creation failed. + *interface_ptr = NULL; + return E_NOINTERFACE; + } + } + *interface_ptr = static_cast<IAccessible*>(accessibility_info_); + return S_OK; + } + // No supported interface found, return error. + *interface_ptr = NULL; + return E_NOINTERFACE; +} + +STDMETHODIMP AccessibleWrapper::SetInstance(IAccessible* interface_ptr) { + if (!interface_ptr) + return E_NOINTERFACE; + + accessibility_info_.Attach(interface_ptr); + + // Paranoia check, to make sure we do have a valid IAccessible pointer stored. + if (!accessibility_info_) + return E_FAIL; + + return S_OK; +} diff --git a/chrome/views/accessibility/accessible_wrapper.h b/chrome/views/accessibility/accessible_wrapper.h new file mode 100644 index 0000000..05a4d6a --- /dev/null +++ b/chrome/views/accessibility/accessible_wrapper.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef CHROME_VIEWS_ACCESSIBILITY_ACCESSIBLE_WRAPPER_H__ +#define CHROME_VIEWS_ACCESSIBILITY_ACCESSIBLE_WRAPPER_H__ + +#include <atlcomcli.h> +#include <oleacc.h> +#include <windows.h> + +#include "base/basictypes.h" + +namespace ChromeViews { + class View; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// AccessibleWrapper +// +// Wrapper class for returning a pointer to the appropriate (platform-specific) +// accessibility interface for a given View. Needed to keep platform-specific +// code out of the View class, when answering calls for child/parent IAccessible +// implementations, for instance. +// +//////////////////////////////////////////////////////////////////////////////// +class AccessibleWrapper { + public: + explicit AccessibleWrapper(ChromeViews::View* view); + ~AccessibleWrapper() {} + + STDMETHODIMP CreateDefaultInstance(REFIID iid); + + // Returns a pointer to a specified interface on an object to which a client + // currently holds an interface pointer. If pointer exists, it is reused, + // otherwise a new pointer is created. Used by accessibility implementation to + // retrieve MSAA implementation for child or parent, when navigating MSAA + // hierarchy. + STDMETHODIMP GetInstance(REFIID iid, void** interface_ptr); + + // Sets the accessibility interface implementation of this wrapper to be + // anything the user specifies. + STDMETHODIMP SetInstance(IAccessible* interface_ptr); + + private: + // Instance of accessibility information and handling for a View. + CComPtr<IAccessible> accessibility_info_; + + // View needed to initialize IAccessible. + ChromeViews::View* view_; + + DISALLOW_EVIL_CONSTRUCTORS(AccessibleWrapper); +}; +#endif // CHROME_VIEWS_ACCESSIBILITY_ACCESSIBLE_WRAPPER_H__ diff --git a/chrome/views/accessibility/autocomplete_accessibility.cc b/chrome/views/accessibility/autocomplete_accessibility.cc new file mode 100644 index 0000000..cb88ff6 --- /dev/null +++ b/chrome/views/accessibility/autocomplete_accessibility.cc @@ -0,0 +1,291 @@ +// 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 "chrome/views/accessibility/autocomplete_accessibility.h" + +#include "base/logging.h" +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +HRESULT AutocompleteAccessibility::Initialize( + const AutocompleteEdit* edit_box) { + if (edit_box == NULL) { + return E_INVALIDARG; + } + + edit_box_ = edit_box; + + // Create a default accessible object for this instance. + return CreateStdAccessibleObject(edit_box_->m_hWnd, OBJID_CLIENT, + IID_IAccessible, reinterpret_cast<void **> + (&default_accessibility_server_)); +} + +STDMETHODIMP AutocompleteAccessibility::get_accChildCount(LONG* child_count) { + if (!child_count) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + return default_accessibility_server_->get_accChildCount(child_count); +} + +STDMETHODIMP AutocompleteAccessibility::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + if (var_child.vt != VT_I4 || !disp_child) { + return E_INVALIDARG; + } + + // If var_child is the parent, remain with the same IDispatch + if (var_child.lVal == CHILDID_SELF) + return S_OK; + + *disp_child = NULL; + return S_FALSE; +} + +STDMETHODIMP AutocompleteAccessibility::get_accParent(IDispatch** disp_parent) { + if (!disp_parent) { + return E_INVALIDARG; + } + + if (edit_box_->parent_view() == NULL) { + *disp_parent = NULL; + return S_FALSE; + } + + // Retrieve the IUnknown interface for the parent view, and assign the + // IDispatch returned. + if (edit_box_->parent_view()->GetAccessibleWrapper()->GetInstance( + IID_IAccessible, reinterpret_cast<void**>(disp_parent)) == S_OK) { + // Increment the reference count for the retrieved interface. + (*disp_parent)->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } +} + +STDMETHODIMP AutocompleteAccessibility::accNavigate(LONG nav_dir, VARIANT start, + VARIANT* end) { + if (start.vt != VT_I4 || !end) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + return default_accessibility_server_->accNavigate(nav_dir, start, end); +} + +STDMETHODIMP AutocompleteAccessibility::get_accFocus(VARIANT* focus_child) { + if (!focus_child) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + return default_accessibility_server_->get_accFocus(focus_child); +} + +STDMETHODIMP AutocompleteAccessibility::get_accName(VARIANT var_id, + BSTR* name) { + if (var_id.vt != VT_I4 || !name) { + return E_INVALIDARG; + } + + std::wstring temp_name = l10n_util::GetString(IDS_ACCNAME_LOCATION); + + if (!temp_name.empty()) { + // Return name retrieved. + *name = CComBSTR(temp_name.c_str()).Detach(); + } else { + // If no name is found, return S_FALSE. + return S_FALSE; + } + DCHECK(*name); + + return S_OK; +} + +STDMETHODIMP AutocompleteAccessibility::get_accDescription(VARIANT var_id, + BSTR* desc) { + if (var_id.vt != VT_I4 || !desc) { + return E_INVALIDARG; + } + + return S_FALSE; +} + +STDMETHODIMP AutocompleteAccessibility::get_accValue(VARIANT var_id, + BSTR* value) { + if (var_id.vt != VT_I4 || !value) { + return E_INVALIDARG; + } + + std::wstring temp_value; + + if (var_id.lVal != CHILDID_SELF) + return E_INVALIDARG; + + // Edit box has no children, only handle self. + temp_value = edit_box_->GetText(); + if (temp_value.empty()) + return S_FALSE; + + // Return value retrieved. + *value = CComBSTR(temp_value.c_str()).Detach(); + + DCHECK(*value); + + return S_OK; + +} + +STDMETHODIMP AutocompleteAccessibility::get_accState(VARIANT var_id, + VARIANT* state) { + if (var_id.vt != VT_I4 || !state) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + HRESULT hr = default_accessibility_server_->get_accState(var_id, state); + + if (hr != S_OK) + return hr; + + // Adding on state to convey the fact that there is a dropdown. + state->lVal |= STATE_SYSTEM_HASPOPUP; + return S_OK; +} + +STDMETHODIMP AutocompleteAccessibility::get_accRole(VARIANT var_id, + VARIANT* role) { + if (var_id.vt != VT_I4 || !role) { + return E_INVALIDARG; + } + + role->vt = VT_I4; + + // Need to override the default role, which is ROLE_SYSTEM_CLIENT. + if (var_id.lVal == CHILDID_SELF) { + role->lVal = ROLE_SYSTEM_TEXT; + } else { + return S_FALSE; + } + + return S_OK; +} + +STDMETHODIMP AutocompleteAccessibility::get_accDefaultAction(VARIANT var_id, + BSTR* def_action) { + if (var_id.vt != VT_I4 || !def_action) { + return E_INVALIDARG; + } + + return S_FALSE; +} + +STDMETHODIMP AutocompleteAccessibility::accLocation(LONG* x_left, LONG* y_top, + LONG* width, LONG* height, + VARIANT var_id) { + if (var_id.vt != VT_I4 || !x_left || !y_top || !width || !height) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + return default_accessibility_server_->accLocation(x_left, y_top, width, + height, var_id); +} + +STDMETHODIMP AutocompleteAccessibility::accHitTest(LONG x_left, LONG y_top, + VARIANT* child) { + if (!child) { + return E_INVALIDARG; + } + + DCHECK(default_accessibility_server_); + return default_accessibility_server_->accHitTest(x_left, y_top, child); +} + +STDMETHODIMP AutocompleteAccessibility::get_accKeyboardShortcut(VARIANT var_id, + BSTR* acc_key) { + if (var_id.vt != VT_I4 || !acc_key) { + return E_INVALIDARG; + } + + return S_FALSE; +} + +// IAccessible functions not supported. + +HRESULT AutocompleteAccessibility::accDoDefaultAction(VARIANT var_id) { + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::get_accSelection(VARIANT* selected) { + if (selected) + selected->vt = VT_EMPTY; + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::accSelect(LONG flagsSelect, + VARIANT var_id) { + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::get_accHelp(VARIANT var_id, + BSTR* help) { + if (help) + *help = NULL; + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id) { + if (help_file) { + *help_file = NULL; + } + if (topic_id) { + *topic_id = static_cast<LONG>(-1); + } + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::put_accName(VARIANT var_id, + BSTR put_name) { + // Deprecated. + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP AutocompleteAccessibility::put_accValue(VARIANT var_id, + BSTR put_val) { + // Deprecated. + return DISP_E_MEMBERNOTFOUND; +} + diff --git a/chrome/views/accessibility/autocomplete_accessibility.h b/chrome/views/accessibility/autocomplete_accessibility.h new file mode 100644 index 0000000..218dd0a --- /dev/null +++ b/chrome/views/accessibility/autocomplete_accessibility.h @@ -0,0 +1,139 @@ +// 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. + +#ifndef CHROME_BROWSER_ACCESSIBILITY_AUTOCOMPLETE_ACCESSIBILITY_H__ +#define CHROME_BROWSER_ACCESSIBILITY_AUTOCOMPLETE_ACCESSIBILITY_H__ + +#include <atlbase.h> +#include <atlcom.h> + +#include <oleacc.h> + +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/views/view.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// AutocompleteAccessibility +// +// Class implementing the MSAA IAccessible COM interface for AutocompleteEdit, +// providing accessibility to be used by screen readers and other assistive +// technology (AT). +// +//////////////////////////////////////////////////////////////////////////////// +class ATL_NO_VTABLE AutocompleteAccessibility + : public CComObjectRootEx<CComMultiThreadModel>, + public IDispatchImpl<IAccessible, &IID_IAccessible, &LIBID_Accessibility> { + public: + BEGIN_COM_MAP(AutocompleteAccessibility) + COM_INTERFACE_ENTRY2(IDispatch, IAccessible) + COM_INTERFACE_ENTRY(IAccessible) + END_COM_MAP() + + AutocompleteAccessibility() {} + ~AutocompleteAccessibility() {} + + HRESULT Initialize(const AutocompleteEdit* edit_box); + + // Supported IAccessible methods. + + // Retrieves the number of accessible children. + STDMETHODIMP get_accChildCount(LONG* child_count); + + // Retrieves an IDispatch interface pointer for the specified child. + STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child); + + // Retrieves the IDispatch interface of the object's parent. + STDMETHODIMP get_accParent(IDispatch** disp_parent); + + // Traverses to another UI element and retrieves the object. + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end); + + // Retrieves the object that has the keyboard focus. + STDMETHODIMP get_accFocus(VARIANT* focus_child); + + // Retrieves the name of the specified object. + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); + + // Retrieves the tooltip description. + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); + + // Returns the current value of the edit box. + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + + // Retrieves the current state of the specified object. + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Retrieves information describing the role of the specified object. + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); + + // Retrieves a string that describes the object's default action. + STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action); + + // Retrieves the specified object's current screen location. + STDMETHODIMP accLocation(LONG* x_left, LONG* y_top, LONG* width, LONG* height, + VARIANT var_id); + + // Retrieves the child element or child object at a given point on the screen. + STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child); + + // Retrieves the specified object's shortcut. + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key); + + // Non-supported IAccessible methods. + + // Out-dated and can be safely said to be very rarely used. + STDMETHODIMP accDoDefaultAction(VARIANT var_id); + + // Selections not applicable to views. + STDMETHODIMP get_accSelection(VARIANT* selected); + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); + + // Help functions not supported. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help); + STDMETHODIMP get_accHelpTopic(BSTR* help_file, VARIANT var_id, + LONG* topic_id); + + // Deprecated functions, not implemented here. + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name); + STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val); + + protected: + // A pointer containing the Windows' default IAccessible implementation for + // this object. Used where it is acceptable to return default MSAA + // information. + CComPtr<IAccessible> default_accessibility_server_; + + private: + const AutocompleteEdit* edit_box_; + + DISALLOW_EVIL_CONSTRUCTORS(AutocompleteAccessibility); +}; +#endif // CHROME_BROWSER_ACCESSIBILITY_AUTOCOMPLETE_ACCESSIBILITY_H__ + diff --git a/chrome/views/accessibility/view_accessibility.cc b/chrome/views/accessibility/view_accessibility.cc new file mode 100644 index 0000000..c435f1e --- /dev/null +++ b/chrome/views/accessibility/view_accessibility.cc @@ -0,0 +1,703 @@ +// 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 "chrome/views/accessibility/view_accessibility.h" + +#include "base/logging.h" +#include "chrome/browser/view_ids.h" +#include "chrome/browser/views/location_bar_view.h" + +HRESULT ViewAccessibility::Initialize(ChromeViews::View* view) { + if (!view) { + return E_INVALIDARG; + } + + view_ = view; + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accChildCount(LONG* child_count) { + if (!child_count) { + return E_INVALIDARG; + } + + DCHECK(view_); + *child_count = view_->GetChildViewCount(); + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + if (var_child.vt != VT_I4 || !disp_child) { + return E_INVALIDARG; + } + + // If var_child is the parent, remain with the same IDispatch. + if (var_child.lVal == CHILDID_SELF) { + return S_OK; + } + + ChromeViews::View* child = NULL; + bool get_iaccessible = false; + + // Check to see if child is out-of-bounds. + if (IsValidChild((var_child.lVal - 1), view_)) { + child = view_->GetChildViewAt(var_child.lVal - 1); + } else { + // Child is further down the hierarchy, get ID and adjust for MSAA. + child = view_->GetViewByID(static_cast<int>(var_child.lVal)); + get_iaccessible = true; + } + // TODO(klink): Add bounds checking for View IDs and an else for OOB error. + + if (!child) { + // No child found. + *disp_child = NULL; + return E_FAIL; + } + + // Sprecial case to handle the AutocompleteEdit MSAA. + if (child->GetID() == VIEW_ID_AUTOCOMPLETE) { + ChromeViews::View* parent = child->GetParent(); + + // Paranoia check, to make sure we are making a correct cast. + if (parent->GetID() == VIEW_ID_LOCATION_BAR) { + LocationBarView* location_bar = + static_cast<LocationBarView*>(parent); + + // Set the custom IAccessible for the HWNDView containing + // AutocompleteEdit. + IAccessible* location_entry_accessibility = + location_bar->location_entry()->GetIAccessible(); + if (!location_entry_accessibility) + return E_NOINTERFACE; + + GetAccessibleWrapper(child)->SetInstance(location_entry_accessibility); + // Setting bool to be true, as we have inserted an IAccessible on a + // leaf, and we need ref counting to happen properly. + get_iaccessible = true; + } + } + + if (get_iaccessible || child->GetChildViewCount() != 0) { + // Retrieve the IUnknown interface for the requested child view, and + // assign the IDispatch returned. + if ((GetAccessibleWrapper(child))-> + GetInstance(IID_IAccessible, + reinterpret_cast<void**>(disp_child)) == S_OK) { + // Increment the reference count for the retrieved interface. + (*disp_child)->AddRef(); + return S_OK; + } else { + // No interface, return failure. + return E_NOINTERFACE; + } + } else { + // When at a leaf, children are handled by the parent object. + *disp_child = NULL; + return S_FALSE; + } +} + +STDMETHODIMP ViewAccessibility::get_accParent(IDispatch** disp_parent) { + if (!disp_parent) { + return E_INVALIDARG; + } + + ChromeViews::View* parent = view_->GetParent(); + + if (!parent) { + // For a View that has no parent (e.g. root), point the accessible parent to + // the default implementation, to interface with Windows' hierarchy and to + // support calls from e.g. WindowFromAccessibleObject. + HRESULT hr = + ::AccessibleObjectFromWindow(view_->GetViewContainer()->GetHWND(), + OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void**>(disp_parent)); + + if (!SUCCEEDED(hr)) { + *disp_parent = NULL; + return S_FALSE; + } + + return S_OK; + } + + // Retrieve the IUnknown interface for the parent view, and assign the + // IDispatch returned. + if ((GetAccessibleWrapper(parent))-> + GetInstance(IID_IAccessible, + reinterpret_cast<void**>(disp_parent)) == S_OK) { + // Increment the reference count for the retrieved interface. + (*disp_parent)->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } +} + +STDMETHODIMP ViewAccessibility::accNavigate(LONG nav_dir, VARIANT start, + VARIANT* end) { + if (start.vt != VT_I4 || !end) { + return E_INVALIDARG; + } + + switch (nav_dir) { + case NAVDIR_FIRSTCHILD: + case NAVDIR_LASTCHILD: { + if (start.lVal != CHILDID_SELF) { + // Start of navigation must be on the View itself. + return E_INVALIDARG; + } else if (view_->GetChildViewCount() == 0) { + // No children found. + return S_FALSE; + } + + // Set child_id based on first or last child. + int child_id = 0; + if (nav_dir == NAVDIR_LASTCHILD) { + child_id = view_->GetChildViewCount() - 1; + } + + ChromeViews::View* child = view_->GetChildViewAt(child_id); + + if (child->GetChildViewCount() != 0) { + end->vt = VT_DISPATCH; + if ((GetAccessibleWrapper(child))-> + GetInstance(IID_IAccessible, + reinterpret_cast<void**>(&end->pdispVal)) == S_OK) { + // Increment the reference count for the retrieved interface. + end->pdispVal->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } + } else { + end->vt = VT_I4; + // Set return child lVal, adjusted for MSAA indexing. (MSAA + // child indexing starts with 1, whereas View indexing starts with 0). + end->lVal = child_id + 1; + } + break; + } + case NAVDIR_LEFT: + case NAVDIR_UP: + case NAVDIR_PREVIOUS: + case NAVDIR_RIGHT: + case NAVDIR_DOWN: + case NAVDIR_NEXT: { + // Retrieve parent to access view index and perform bounds checking. + ChromeViews::View* parent = view_->GetParent(); + if (!parent) { + return E_FAIL; + } + + if (start.lVal == CHILDID_SELF) { + int view_index = parent->GetChildIndex(view_); + // Check navigation bounds, adjusting for View child indexing (MSAA + // child indexing starts with 1, whereas View indexing starts with 0). + if (!IsValidNav(nav_dir, view_index, -1, + parent->GetChildViewCount())) { + // Navigation attempted to go out-of-bounds. + end->vt = VT_EMPTY; + return S_FALSE; + } else { + if (IsNavDirNext(nav_dir)) { + view_index += 1; + } else { + view_index -=1; + } + } + + ChromeViews::View* child = parent->GetChildViewAt(view_index); + if (child->GetChildViewCount() != 0) { + end->vt = VT_DISPATCH; + // Retrieve IDispatch for non-leaf child. + if ((GetAccessibleWrapper(child))-> + GetInstance(IID_IAccessible, + reinterpret_cast<void**>(&end->pdispVal)) == S_OK) { + // Increment the reference count for the retrieved interface. + end->pdispVal->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } + } else { + end->vt = VT_I4; + // Modifying view_index to give lVal correct MSAA-based value. (MSAA + // child indexing starts with 1, whereas View indexing starts with 0). + end->lVal = view_index + 1; + } + } else { + // Check navigation bounds, adjusting for MSAA child indexing (MSAA + // child indexing starts with 1, whereas View indexing starts with 0). + if (!IsValidNav(nav_dir, start.lVal, 0, + parent->GetChildViewCount() + 1)) { + // Navigation attempted to go out-of-bounds. + end->vt = VT_EMPTY; + return S_FALSE; + } else { + if (IsNavDirNext(nav_dir)) { + start.lVal += 1; + } else { + start.lVal -= 1; + } + } + + HRESULT result = this->get_accChild(start, &end->pdispVal); + if (result == S_FALSE) { + // Child is a leaf. + end->vt = VT_I4; + end->lVal = start.lVal; + } else if (result == E_INVALIDARG) { + return E_INVALIDARG; + } else { + // Child is not a leaf. + end->vt = VT_DISPATCH; + } + } + break; + } + default: + return E_INVALIDARG; + } + // Navigation performed correctly. Global return for this function, if no + // error triggered an escape earlier. + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accFocus(VARIANT* focus_child) { + if (!focus_child) { + return E_INVALIDARG; + } + + if (view_->GetChildViewCount() == 0 && view_->HasFocus()) { + // Parent view has focus. + focus_child->vt = VT_I4; + focus_child->lVal = CHILDID_SELF; + } else { + bool has_focus = false; + int child_count = view_->GetChildViewCount(); + // Search for child view with focus. + for (int child_id = 0; child_id < child_count; ++child_id) { + if (view_->GetChildViewAt(child_id)->HasFocus()) { + focus_child->vt = VT_I4; + focus_child->lVal = child_id + 1; + + // If child view is no leaf, retrieve IDispatch. + if (view_->GetChildViewAt(child_id)->GetChildViewCount() != 0) { + focus_child->vt = VT_DISPATCH; + this->get_accChild(*focus_child, &focus_child->pdispVal); + } + has_focus = true; + break; + } + } + // No current focus on any of the children. + if (!has_focus) { + focus_child->vt = VT_EMPTY; + return S_FALSE; + } + } + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accName(VARIANT var_id, BSTR* name) { + if (var_id.vt != VT_I4 || !name) { + return E_INVALIDARG; + } + + std::wstring temp_name; + + if (var_id.lVal == CHILDID_SELF) { + // Retrieve the parent view's name. + view_->GetAccessibleName(&temp_name); + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + // Retrieve the child view's name. + view_->GetChildViewAt(var_id.lVal - 1)->GetAccessibleName(&temp_name); + } + if (!temp_name.empty()) { + // Return name retrieved. + *name = CComBSTR(temp_name.c_str()).Detach(); + } else { + // If view has no name, return S_FALSE. + return S_FALSE; + } + DCHECK(*name); + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accDescription(VARIANT var_id, BSTR* desc) { + if (var_id.vt != VT_I4 || !desc) { + return E_INVALIDARG; + } + + std::wstring temp_desc; + + if (var_id.lVal == CHILDID_SELF) { + view_->GetTooltipText(0, 0, &temp_desc); + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + view_->GetChildViewAt(var_id.lVal - 1)->GetTooltipText(0, 0, &temp_desc); + } + if (!temp_desc.empty()) { + *desc = CComBSTR(temp_desc.c_str()).Detach(); + } else { + return S_FALSE; + } + DCHECK(*desc); + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accState(VARIANT var_id, VARIANT* state) { + if (var_id.vt != VT_I4 || !state) { + return E_INVALIDARG; + } + + state->vt = VT_I4; + + if (var_id.lVal == CHILDID_SELF) { + // Retrieve all currently applicable states of the parent. + this->SetState(state, view_); + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + // Retrieve all currently applicable states of the child. + this->SetState(state, view_->GetChildViewAt(var_id.lVal - 1)); + } + DCHECK((*state).vt != VT_EMPTY); + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accRole(VARIANT var_id, VARIANT* role) { + if (var_id.vt != VT_I4 || !role) { + return E_INVALIDARG; + } + + if (var_id.lVal == CHILDID_SELF) { + // Retrieve parent role. + if (!view_->GetAccessibleRole(role)) { + return E_FAIL; + } + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + // Retrieve child role. + if (!view_->GetChildViewAt(var_id.lVal - 1)->GetAccessibleRole(role)) { + return E_FAIL; + } + } + DCHECK((*role).vt != VT_EMPTY); + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accDefaultAction(VARIANT var_id, + BSTR* def_action) { + if (var_id.vt != VT_I4 || !def_action) { + return E_INVALIDARG; + } + + std::wstring temp_action; + + if (var_id.lVal == CHILDID_SELF) { + view_->GetAccessibleDefaultAction(&temp_action); + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + view_->GetChildViewAt(var_id.lVal - 1)-> + GetAccessibleDefaultAction(&temp_action); + } + if (!temp_action.empty()) { + *def_action = CComBSTR(temp_action.c_str()).Detach(); + } else { + return S_FALSE; + } + DCHECK(*def_action); + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::accLocation(LONG* x_left, LONG* y_top, + LONG* width, LONG* height, + VARIANT var_id) { + if (var_id.vt != VT_I4 || !x_left || !y_top || !width || !height) { + return E_INVALIDARG; + } + + CRect view_bounds(0, 0, 0, 0); + // Retrieving the parent View to be used for converting from view-to-screen + // coordinates. + ChromeViews::View* parent = view_->GetParent(); + + if (parent == NULL) { + // If no parent, remain within the same View. + parent = view_; + } + + if (var_id.lVal == CHILDID_SELF) { + // Retrieve active View's bounds. + view_->GetBounds(&view_bounds); + } else { + // Check to see if child is out-of-bounds. + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + // Retrieve child bounds. + view_->GetChildViewAt(var_id.lVal - 1)->GetBounds(&view_bounds); + // Parent View is current View. + parent = view_; + } + + if (!view_bounds.IsRectNull()) { + *width = view_bounds.Width(); + *height = view_bounds.Height(); + + ChromeViews::View::ConvertPointToScreen(parent, &view_bounds.TopLeft()); + *x_left = view_bounds.left; + *y_top = view_bounds.top; + } else { + return E_FAIL; + } + return S_OK; +} + + +// TODO(klink): Handle case where child View is not contained by parent. +STDMETHODIMP ViewAccessibility::accHitTest(LONG x_left, LONG y_top, + VARIANT* child) { + if (!child) { + return E_INVALIDARG; + } + + CPoint pt(x_left, y_top); + ChromeViews::View::ConvertPointToView(NULL, view_, &pt); + + if (!view_->HitTest(pt)) { + // If containing parent is not hit, return with failure. + child->vt = VT_EMPTY; + return S_FALSE; + } + + int child_count = view_->GetChildViewCount(); + bool child_hit = false; + ChromeViews::View* child_view = NULL; + for (int child_id = 0; child_id < child_count; ++child_id) { + // Search for hit within any of the children. + child_view = view_->GetChildViewAt(child_id); + ChromeViews::View::ConvertPointToView(view_, child_view, &pt); + if (child_view->HitTest(pt)) { + // Store child_id (adjusted with +1 to convert to MSAA indexing). + child->lVal = child_id + 1; + child_hit = true; + break; + } + // Convert point back to parent view to test next child. + ChromeViews::View::ConvertPointToView(child_view, view_, &pt); + } + + child->vt = VT_I4; + + if (!child_hit) { + // No child hit, return parent id. + child->lVal = CHILDID_SELF; + } else { + if (child_view == NULL) { + return E_FAIL; + } else if (child_view->GetChildViewCount() != 0) { + // Retrieve IDispatch for child, if it is not a leaf. + child->vt = VT_DISPATCH; + if ((GetAccessibleWrapper(child_view))-> + GetInstance(IID_IAccessible, + reinterpret_cast<void**>(&child->pdispVal)) == S_OK) { + // Increment the reference count for the retrieved interface. + child->pdispVal->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + } + + return S_OK; +} + +STDMETHODIMP ViewAccessibility::get_accKeyboardShortcut(VARIANT var_id, + BSTR* acc_key) { + if (var_id.vt != VT_I4 || !acc_key) { + return E_INVALIDARG; + } + + std::wstring temp_key; + + if (var_id.lVal == CHILDID_SELF) { + view_->GetAccessibleKeyboardShortcut(&temp_key); + } else { + if (!IsValidChild((var_id.lVal - 1), view_)) { + return E_INVALIDARG; + } + view_->GetChildViewAt(var_id.lVal - 1)-> + GetAccessibleKeyboardShortcut(&temp_key); + } + if (!temp_key.empty()) { + *acc_key = CComBSTR(temp_key.c_str()).Detach(); + } else { + return S_FALSE; + } + DCHECK(*acc_key); + + return S_OK; +} + +// Helper functions. + +bool ViewAccessibility::IsValidChild(int child_id, + ChromeViews::View* view) const { + if (((child_id) < 0) || + ((child_id) >= view->GetChildViewCount())) { + return false; + } + return true; +} + +bool ViewAccessibility::IsNavDirNext(int nav_dir) const { + if (nav_dir == NAVDIR_RIGHT || nav_dir == NAVDIR_DOWN || + nav_dir == NAVDIR_NEXT) { + return true; + } + return false; +} + +bool ViewAccessibility::IsValidNav(int nav_dir, int start_id, int lower_bound, + int upper_bound) const { + if (IsNavDirNext(nav_dir)) { + if ((start_id + 1) > upper_bound) { + return false; + } + } else { + if ((start_id - 1) <= lower_bound) { + return false; + } + } + return true; +} + +void ViewAccessibility::SetState(VARIANT* state, ChromeViews::View* view) { + // Default state; all views can have accessibility focus. + state->lVal |= STATE_SYSTEM_FOCUSABLE; + + if (!view) + return; + + if (!view->IsEnabled()) { + state->lVal |= STATE_SYSTEM_UNAVAILABLE; + } + if (!view->IsVisible()) { + state->lVal |= STATE_SYSTEM_INVISIBLE; + } + if (view->IsHotTracked()) { + state->lVal |= STATE_SYSTEM_HOTTRACKED; + } + if (view->IsPushed()) { + state->lVal |= STATE_SYSTEM_PRESSED; + } + // Check both for actual View focus, as well as accessibility focus. + ChromeViews::View* parent = view->GetParent(); + + if (view->HasFocus() || + (parent && parent->GetAccFocusedChildView() == view)) { + state->lVal |= STATE_SYSTEM_FOCUSED; + } + + // Add on any view-specific states. + view->GetAccessibleState(state); +} + +// IAccessible functions not supported. + +HRESULT ViewAccessibility::accDoDefaultAction(VARIANT var_id) { + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::get_accValue(VARIANT var_id, BSTR* value) { + if (value) + *value = NULL; + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::get_accSelection(VARIANT* selected) { + if (selected) + selected->vt = VT_EMPTY; + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::accSelect(LONG flagsSelect, VARIANT var_id) { + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::get_accHelp(VARIANT var_id, BSTR* help) { + if (help) + *help = NULL; + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id) { + if (help_file) { + *help_file = NULL; + } + if (topic_id) { + *topic_id = static_cast<LONG>(-1); + } + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::put_accName(VARIANT var_id, BSTR put_name) { + // Deprecated. + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP ViewAccessibility::put_accValue(VARIANT var_id, BSTR put_val) { + // Deprecated. + return DISP_E_MEMBERNOTFOUND; +} + diff --git a/chrome/views/accessibility/view_accessibility.h b/chrome/views/accessibility/view_accessibility.h new file mode 100644 index 0000000..6dcf9dcb --- /dev/null +++ b/chrome/views/accessibility/view_accessibility.h @@ -0,0 +1,155 @@ +// 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. + +#ifndef CHROME_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H__ +#define CHROME_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H__ + +#include <atlbase.h> +#include <atlcom.h> + +#include <oleacc.h> + +#include "chrome/views/view.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// ViewAccessibility +// +// Class implementing the MSAA IAccessible COM interface for a generic View, +// providing accessibility to be used by screen readers and other assistive +// technology (AT). +// +//////////////////////////////////////////////////////////////////////////////// +class ATL_NO_VTABLE ViewAccessibility + : public CComObjectRootEx<CComMultiThreadModel>, + public IDispatchImpl<IAccessible, &IID_IAccessible, &LIBID_Accessibility> { + public: + BEGIN_COM_MAP(ViewAccessibility) + COM_INTERFACE_ENTRY2(IDispatch, IAccessible) + COM_INTERFACE_ENTRY(IAccessible) + END_COM_MAP() + + ViewAccessibility() {} + ~ViewAccessibility() {} + + HRESULT Initialize(ChromeViews::View* view); + + // Supported IAccessible methods. + + // Retrieves the number of accessible children. + STDMETHODIMP get_accChildCount(LONG* child_count); + + // Retrieves an IDispatch interface pointer for the specified child. + STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child); + + // Retrieves the IDispatch interface of the object's parent. + STDMETHODIMP get_accParent(IDispatch** disp_parent); + + // Traverses to another UI element and retrieves the object. + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end); + + // Retrieves the object that has the keyboard focus. + STDMETHODIMP get_accFocus(VARIANT* focus_child); + + // Retrieves the name of the specified object. + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); + + // Retrieves the tooltip description. + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); + + // Retrieves the current state of the specified object. + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Retrieves information describing the role of the specified object. + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); + + // Retrieves a string that describes the object's default action. + STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action); + + // Retrieves the specified object's current screen location. + STDMETHODIMP accLocation(LONG* x_left, LONG* y_top, LONG* width, LONG* height, + VARIANT var_id); + + // Retrieves the child element or child object at a given point on the screen. + STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child); + + // Retrieves the specified object's shortcut. + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key); + + // Non-supported IAccessible methods. + + // Out-dated and can be safely said to be very rarely used. + STDMETHODIMP accDoDefaultAction(VARIANT var_id); + + // No value associated with views. + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + + // Selections not applicable to views. + STDMETHODIMP get_accSelection(VARIANT* selected); + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); + + // Help functions not supported. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help); + STDMETHODIMP get_accHelpTopic(BSTR* help_file, VARIANT var_id, + LONG* topic_id); + + // Deprecated functions, not implemented here. + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name); + STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val); + + private: + // Checks to see if child_id is within the child bounds of view. Returns true + // if the child is within the bounds, false otherwise. + bool IsValidChild(int child_id, ChromeViews::View* view) const; + + // Determines navigation direction for accNavigate, based on left, up and + // previous being mapped all to previous and right, down, next being mapped + // to next. Returns true if navigation direction is next, false otherwise. + bool IsNavDirNext(int nav_dir) const; + + // Determines if the navigation target is within the allowed bounds. Returns + // true if it is, false otherwise. + bool IsValidNav(int nav_dir, int start_id, int lower_bound, + int upper_bound) const; + + // Wrapper to retrieve the view's instance of IAccessible. + AccessibleWrapper* GetAccessibleWrapper(ChromeViews::View* view) const { + return view->GetAccessibleWrapper(); + } + + // Helper function which sets applicable states of view. + void SetState(VARIANT* state, ChromeViews::View* view); + + // Member View needed for view-specific calls. + ChromeViews::View* view_; + + DISALLOW_EVIL_CONSTRUCTORS(ViewAccessibility); +}; +#endif // CHROME_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H__ + diff --git a/chrome/views/aero_tooltip_manager.cc b/chrome/views/aero_tooltip_manager.cc new file mode 100644 index 0000000..a6f3ec5 --- /dev/null +++ b/chrome/views/aero_tooltip_manager.cc @@ -0,0 +1,144 @@ +// 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 <windows.h> +#include <atlbase.h> +#include <atlapp.h> // for GET_X/Y_LPARAM +#include <commctrl.h> +#include <shlobj.h> + +#include "chrome/views/aero_tooltip_manager.h" + +#include "base/message_loop.h" +#include "chrome/common/l10n_util.h" + +namespace ChromeViews { + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, public: + +AeroTooltipManager::AeroTooltipManager(ViewContainer* container, HWND parent) + : TooltipManager(container, parent), + initial_delay_(0) { +} + +AeroTooltipManager::~AeroTooltipManager() { + if (initial_timer_) + initial_timer_->Disown(); +} + +void AeroTooltipManager::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + if (initial_timer_) + initial_timer_->Disown(); + + if (u_msg == WM_MOUSEMOVE || u_msg == WM_NCMOUSEMOVE) { + int x = GET_X_LPARAM(l_param); + int y = GET_Y_LPARAM(l_param); + if (last_mouse_x_ != x || last_mouse_y_ != y) { + last_mouse_x_ = x; + last_mouse_y_ = y; + UpdateTooltip(x, y); + } + + // Delay opening of the tooltip just in case the user moves their + // mouse to another control. We defer this from Init because we get + // zero if we query it too soon. + if (!initial_delay_) { + initial_delay_ = static_cast<int>( + ::SendMessage(tooltip_hwnd_, TTM_GETDELAYTIME, TTDT_INITIAL, 0)); + } + initial_timer_ = new InitialTimer(this, initial_delay_); + } else { + // Hide the tooltip and cancel any timers. + ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + ::SendMessage(tooltip_hwnd_, TTM_TRACKACTIVATE, false, (LPARAM)&toolinfo_); + return; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, private: + +void AeroTooltipManager::Init() { + // Create the tooltip control. + tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, + parent_, NULL, NULL, NULL); + // Add one tool that is used for all tooltips. + toolinfo_.cbSize = sizeof(toolinfo_); + + // We use tracking tooltips on Vista to allow us to manually control the + // visibility of the tooltip. + toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE; + toolinfo_.hwnd = parent_; + toolinfo_.uId = (UINT_PTR)parent_; + + // Setting this tells windows to call parent_ back (using a WM_NOTIFY + // message) for the actual tooltip contents. + toolinfo_.lpszText = LPSTR_TEXTCALLBACK; + SetRectEmpty(&toolinfo_.rect); + ::SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); +} + +void AeroTooltipManager::OnTimer() { + initial_timer_ = NULL; + + POINT pt; + pt.x = last_mouse_x_; + pt.y = last_mouse_y_; + ::ClientToScreen(parent_, &pt); + + // Set the position and visibility. + if (!tooltip_showing_) { + ::SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); + ::SendMessage(tooltip_hwnd_, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y)); + ::SendMessage(tooltip_hwnd_, TTM_TRACKACTIVATE, true, (LPARAM)&toolinfo_); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager::InitialTimer + +AeroTooltipManager::InitialTimer::InitialTimer(AeroTooltipManager* manager, + int time) : manager_(manager) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( + this, &InitialTimer::Execute), time); +} + +void AeroTooltipManager::InitialTimer::Disown() { + manager_ = NULL; +} + +void AeroTooltipManager::InitialTimer::Execute() { + if (manager_) + manager_->OnTimer(); +} + +} // namespace ChromeViews diff --git a/chrome/views/aero_tooltip_manager.h b/chrome/views/aero_tooltip_manager.h new file mode 100644 index 0000000..8438fe1 --- /dev/null +++ b/chrome/views/aero_tooltip_manager.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef CHROME_VIEWS_AERO_TOOLTIP_MANAGER_H__ +#define CHROME_VIEWS_AERO_TOOLTIP_MANAGER_H__ + +#include "base/ref_counted.h" +#include "base/task.h" +#include "chrome/views/tooltip_manager.h" + +namespace ChromeViews { + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager +// +// Default Windows tooltips are broken when using our custom window frame +// - as soon as the tooltip receives a WM_MOUSEMOVE event, it starts spewing +// NCHITTEST messages at its parent window (us). These messages have random +// x/y coordinates and can't be ignored, as the DwmDefWindowProc uses +// NCHITTEST messages to determine how to highlight the caption buttons +// (the buttons then flicker as the hit tests sent by the user's mouse +// trigger different effects to those sent by the tooltip). +// +// So instead, we have to partially implement tooltips ourselves using +// TTF_TRACKed tooltips. +// +// TODO(glen): Resolve this with Microsoft. +class AeroTooltipManager : public ChromeViews::TooltipManager { + public: + AeroTooltipManager(ViewContainer* container, HWND parent); + virtual ~AeroTooltipManager(); + + void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param); + + private: + void Init(); + void OnTimer(); + + class InitialTimer : public base::RefCounted<InitialTimer> { + public: + InitialTimer(AeroTooltipManager* manager, int time); + void Disown(); + void Execute(); + + private: + AeroTooltipManager* manager_; + }; + + int initial_delay_; + scoped_refptr<InitialTimer> initial_timer_; +}; + +} // namespace ChromeViews + +#endif // #ifndef CHROME_VIEWS_AERO_TOOLTIP_MANAGER_H__ diff --git a/chrome/views/app_modal_dialog_delegate.h b/chrome/views/app_modal_dialog_delegate.h new file mode 100644 index 0000000..d9ef3e9 --- /dev/null +++ b/chrome/views/app_modal_dialog_delegate.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef CHROME_VIEWS_APP_MODAL_DIALOG_DELEGATE_H__ +#define CHROME_VIEWS_APP_MODAL_DIALOG_DELEGATE_H__ + +#include "chrome/views/dialog_delegate.h" + +namespace ChromeViews { + +// Pure virtual interface for a window which is app modal. +class AppModalDialogDelegate : public DialogDelegate { + public: + // Called by the app modal window queue when it is time to show this window. + virtual void ShowModalDialog() = 0; + + // Called by the app modal window queue to activate the window. + virtual void ActivateModalDialog() = 0; +}; + +} // namespace ChromeViews + +#endif // #ifndef CHROME_VIEWS_APP_MODAL_DIALOG_DELEGATE_H__ diff --git a/chrome/views/background.cc b/chrome/views/background.cc new file mode 100644 index 0000000..ba80361 --- /dev/null +++ b/chrome/views/background.cc @@ -0,0 +1,128 @@ +// 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 "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/background.h" +#include "chrome/views/painter.h" +#include "chrome/views/view.h" +#include "skia/include/SkPaint.h" +#include "base/gfx/skia_utils.h" + +namespace ChromeViews { + +// SolidBackground is a trivial Background implementation that fills the +// background in a solid color. +class SolidBackground : public Background { + public: + explicit SolidBackground(const SkColor& color) : + color_(color) { + SetNativeControlColor(color_); + } + + void Paint(ChromeCanvas* canvas, View* view) const { + // Fill the background. Note that we don't constrain to the bounds as + // canvas is already clipped for us. + canvas->drawColor(color_); + } + + private: + const SkColor color_; + + DISALLOW_EVIL_CONSTRUCTORS(SolidBackground); +}; + +class BackgroundPainter : public ChromeViews::Background { + public: + BackgroundPainter(bool owns_painter, Painter* painter) + : owns_painter_(owns_painter), painter_(painter) { + DCHECK(painter); + } + + virtual ~BackgroundPainter() { + if (owns_painter_) + delete painter_; + } + + + void Paint(ChromeCanvas* canvas, View* view) const { + Painter::PaintPainterAt(0, 0, view->GetWidth(), view->GetHeight(), canvas, + painter_); + } + + private: + bool owns_painter_; + Painter* painter_; + + DISALLOW_EVIL_CONSTRUCTORS(BackgroundPainter); +}; + +Background::Background() : native_control_brush_(NULL) { +} + +Background::~Background() { + DeleteObject(native_control_brush_); +} + +void Background::SetNativeControlColor(SkColor color) { + DeleteObject(native_control_brush_); + native_control_brush_ = CreateSolidBrush(gfx::SkColorToCOLORREF(color)); +} + +//static +Background* Background::CreateSolidBackground(const SkColor& color) { + return new SolidBackground(color); +} + +//static +Background* Background::CreateStandardPanelBackground() { + return CreateVerticalGradientBackground(SkColorSetRGB(246, 250, 255), + SkColorSetRGB(219, 235, 255)); +} + +//static +Background* Background::CreateVerticalGradientBackground( + const SkColor& color1, const SkColor& color2) { + Background *background = + CreateBackgroundPainter(true, Painter::CreateVerticalGradient(color1, + color2)); + background->SetNativeControlColor( // 50% blend of colors 1 & 2 + SkColorSetRGB((SkColorGetR(color1) + SkColorGetR(color2)) / 2, + (SkColorGetG(color1) + SkColorGetG(color2)) / 2, + (SkColorGetB(color1) + SkColorGetB(color2)) / 2)); + + return background; +} + +//static +Background* Background::CreateBackgroundPainter(bool owns_painter, + Painter* painter) { + return new BackgroundPainter(owns_painter, painter); +} + +} diff --git a/chrome/views/background.h b/chrome/views/background.h new file mode 100644 index 0000000..cce513f --- /dev/null +++ b/chrome/views/background.h @@ -0,0 +1,104 @@ +// 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. + +#ifndef CHROME_VIEWS_BACKGROUND_H__ +#define CHROME_VIEWS_BACKGROUND_H__ + +#include <windows.h> + +#include "base/basictypes.h" +#include "SkColor.h" + +class ChromeCanvas; + +namespace ChromeViews { + +class Painter; +class View; + +///////////////////////////////////////////////////////////////////////////// +// +// Background class +// +// A background implements a way for views to paint a background. The +// background can be either solid or based on a gradient. Of course, +// Background can be subclassed to implement various effects. +// +// Any View can have a background. See View::SetBackground() and +// View::PaintBackground() +// +///////////////////////////////////////////////////////////////////////////// +class Background { + public: + Background(); + virtual ~Background(); + + // Creates a background that fills the canvas in the specified color. + static Background* CreateSolidBackground(const SkColor& color); + + // Creates a background that fills the canvas in the specified color. + static Background* CreateSolidBackground(int r, int g, int b) { + return CreateSolidBackground(SkColorSetRGB(r, g, b)); + } + + // Creates a background that fills the canvas in the specified color. + static Background* CreateSolidBackground(int r, int g, int b, int a) { + return CreateSolidBackground(SkColorSetARGB(a, r, g, b)); + } + + // Creates a background that contains a vertical gradient that varies + // from |color1| to |color2| + static Background* CreateVerticalGradientBackground(const SkColor& color1, + const SkColor& color2); + + // Creates Chrome's standard panel background + static Background* CreateStandardPanelBackground(); + + // Creates a Background from the specified Painter. If owns_painter is + // true, the Painter is deleted when the Border is deleted. + static Background* CreateBackgroundPainter(bool owns_painter, + Painter* painter); + + // Render the background for the provided view + virtual void Paint(ChromeCanvas* canvas, View* view) const = 0; + + // Set a solid, opaque color to be used when drawing backgrounds of native + // controls. Unfortunately alpha=0 is not an option. + void SetNativeControlColor(SkColor color); + + // Get the brush that was specified by SetNativeControlColor + HBRUSH GetNativeControlBrush() const { return native_control_brush_; }; + + private: + HBRUSH native_control_brush_; + DISALLOW_EVIL_CONSTRUCTORS(Background); +}; + +} +#endif // CHROME_VIEWS_BACKGROUND_H__ diff --git a/chrome/views/base_button.cc b/chrome/views/base_button.cc new file mode 100644 index 0000000..6fc440a --- /dev/null +++ b/chrome/views/base_button.cc @@ -0,0 +1,333 @@ +// 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 <atlbase.h> +#include <atlapp.h> + +#include "chrome/views/base_button.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/os_exchange_data.h" + +namespace ChromeViews { + +// How long the hover animation takes if uninterrupted. +static const int kHoverFadeDurationMs = 150; + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +BaseButton::BaseButton() + : listener_(NULL), + tag_(-1), + state_(BS_NORMAL), + mouse_event_flags_(0), + animate_on_state_change_(true) { + hover_animation_.reset(new ThrobAnimation(this)); + hover_animation_->SetSlideDuration(kHoverFadeDurationMs); +} + +BaseButton::~BaseButton() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton - properties +// +//////////////////////////////////////////////////////////////////////////////// + +bool BaseButton::IsTriggerableEvent(const ChromeViews::MouseEvent& e) { + return e.IsLeftMouseButton(); +} + +void BaseButton::SetState(BaseButton::ButtonState new_state) { + if (new_state != state_) { + if (animate_on_state_change_ || !hover_animation_->IsAnimating()) { + animate_on_state_change_ = true; + if (state_ == BaseButton::BS_NORMAL && new_state == BaseButton::BS_HOT) { + // Button is hovered from a normal state, start hover animation. + hover_animation_->Show(); + } else if (state_ == BaseButton::BS_HOT && + new_state == BaseButton::BS_NORMAL) { + // Button is returning to a normal state from hover, start hover + // fade animation. + hover_animation_->Hide(); + } else { + hover_animation_->Stop(); + } + } + + state_ = new_state; + SchedulePaint(); + } +} + +void BaseButton::SetEnabled(bool f) { + if (f && state_ == BS_DISABLED) { + SetState(BS_NORMAL); + } else if (!f && state_ != BS_DISABLED) { + SetState(BS_DISABLED); + } +} + +void BaseButton::SetAnimationDuration(int duration) { + hover_animation_->SetSlideDuration(duration); +} + +void BaseButton::StartThrobbing(int cycles_til_stop) { + animate_on_state_change_ = false; + hover_animation_->StartThrobbing(cycles_til_stop); +} + +bool BaseButton::IsEnabled() const { + return state_ != BS_DISABLED; +} + +void BaseButton::SetHotTracked(bool f) { + if (f && state_ != BS_DISABLED) { + SetState(BS_HOT); + } else if (!f && state_ != BS_DISABLED) { + SetState(BS_NORMAL); + } +} + +bool BaseButton::IsHotTracked() const { + return state_ == BS_HOT; +} + +bool BaseButton::IsPushed() const { + return state_ == BS_PUSHED; +} + +void BaseButton::SetListener(ButtonListener *l, int tag) { + listener_ = l; + tag_ = tag; +} + +int BaseButton::GetTag() { + return tag_; +} + +bool BaseButton::IsFocusable() const { + return (state_ != BS_DISABLED) && View::IsFocusable(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton - Tooltips +// +//////////////////////////////////////////////////////////////////////////////// + +bool BaseButton::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (!tooltip_text_.empty()) { + *tooltip = tooltip_text_; + return true; + } + return false; +} + +void BaseButton::SetTooltipText(const std::wstring& tooltip) { + tooltip_text_.assign(tooltip); + TooltipTextChanged(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton - Events +// +//////////////////////////////////////////////////////////////////////////////// + +bool BaseButton::OnMousePressed(const ChromeViews::MouseEvent& e) { + if (state_ != BS_DISABLED) { + if (IsTriggerableEvent(e) && HitTest(e.GetLocation())) { + SetState(BS_PUSHED); + } + if (IsFocusable()) + RequestFocus(); + } + return true; +} + +bool BaseButton::OnMouseDragged(const ChromeViews::MouseEvent& e) { + if (state_ != BS_DISABLED) { + if (!HitTest(e.GetLocation())) + SetState(BS_NORMAL); + else if (IsTriggerableEvent(e)) + SetState(BS_PUSHED); + else + SetState(BS_HOT); + } + return true; +} + +void BaseButton::OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled) { + if (InDrag()) { + // Starting a drag results in a MouseReleased, we need to ignore it. + return; + } + + if (state_ != BS_DISABLED) { + if (canceled || !HitTest(e.GetLocation())) { + SetState(BS_NORMAL); + } else { + SetState(BS_HOT); + if (IsTriggerableEvent(e)) { + NotifyClick(e.GetFlags()); + // We may be deleted at this point (by the listener's notification + // handler) so no more doing anything, just return. + return; + } + } + } +} + +void BaseButton::OnMouseEntered(const ChromeViews::MouseEvent& e) { + using namespace ChromeViews; + if (state_ != BS_DISABLED) { + SetState(BS_HOT); + } +} + +void BaseButton::OnMouseMoved(const ChromeViews::MouseEvent& e) { + using namespace ChromeViews; + + if (state_ != BS_DISABLED) { + if (HitTest(e.GetLocation())) { + SetState(BS_HOT); + } else { + SetState(BS_NORMAL); + } + } +} + +void BaseButton::OnMouseExited(const ChromeViews::MouseEvent& e) { + using namespace ChromeViews; + // Starting a drag results in a MouseExited, we need to ignore it. + if (state_ != BS_DISABLED && !InDrag()) { + SetState(BS_NORMAL); + } +} + +void BaseButton::NotifyClick(int mouse_event_flags) { + mouse_event_flags_ = mouse_event_flags; + if (listener_ != NULL) + listener_->ButtonPressed(this); + // NOTE: don't attempt to reset mouse_event_flags_ as the listener may have + // deleted us. +} + +bool BaseButton::OnKeyPressed(const KeyEvent& e) { + if (state_ != BS_DISABLED) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + SetState(BS_PUSHED); + return true; + } + } + return false; +} + +bool BaseButton::OnKeyReleased(const KeyEvent& e) { + if (state_ != BS_DISABLED) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + } + return false; +} + +bool BaseButton::AcceleratorPressed(const Accelerator& accelerator) { + if (enabled_) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + return false; +} + +void BaseButton::AnimationProgressed(const Animation* animation) { + SchedulePaint(); +} + +void BaseButton::OnDragDone() { + SetState(BS_NORMAL); +} + +void BaseButton::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (!is_add && state_ != BS_DISABLED) + SetState(BS_NORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton - Accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool BaseButton::GetAccessibleKeyboardShortcut(std::wstring* shortcut) { + if (!accessible_shortcut_.empty()) { + *shortcut = accessible_shortcut_; + return true; + } + return false; +} + +bool BaseButton::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void BaseButton::SetAccessibleKeyboardShortcut(const std::wstring& shortcut) { + accessible_shortcut_.assign(shortcut); +} + +void BaseButton::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +void BaseButton::Paint(ChromeCanvas* canvas) { + View::Paint(canvas); +} + +void BaseButton::Paint(ChromeCanvas* canvas, bool for_drag) { + Paint(canvas); +} + +} // namespace ChromeViews diff --git a/chrome/views/base_button.h b/chrome/views/base_button.h new file mode 100644 index 0000000..7b655bb --- /dev/null +++ b/chrome/views/base_button.h @@ -0,0 +1,211 @@ +// 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. + +#ifndef CHROME_VIEWS_BASE_BUTTON_H__ +#define CHROME_VIEWS_BASE_BUTTON_H__ + +#include <windows.h> + +#include "base/logging.h" +#include "chrome/common/throb_animation.h" +#include "chrome/views/event.h" +#include "chrome/views/view.h" + +class OSExchangeData; + +namespace ChromeViews { + +class MouseEvent; + +//////////////////////////////////////////////////////////////////////////////// +// +// BaseButton +// +// A base button class that shares common button functionality between various +// specializations. +// +//////////////////////////////////////////////////////////////////////////////// +class BaseButton : public View, + public AnimationDelegate { + public: + // Possible states + typedef enum ButtonState { BS_NORMAL = 0, + BS_HOT = 1, + BS_PUSHED = 2, + BS_DISABLED = 3}; + static const int kButtonStateCount = 4; + + virtual ~BaseButton(); + + // Set / test whether the MenuButton is enabled. + virtual void SetEnabled(bool f); + virtual bool IsEnabled() const; + + // Set / test whether the button is hot-tracked. + void SetHotTracked(bool f); + bool IsHotTracked() const; + + // Set how long the hover animation will last for. + void SetAnimationDuration(int duration); + + // Starts throbbing. See HoverAnimation for a description of cycles_til_stop. + void StartThrobbing(int cycles_til_stop); + + // Returns whether the button is pushed. + bool IsPushed() const; + + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + void SetTooltipText(const std::wstring& tooltip); + + // Overridden from View to take into account the enabled state. + virtual bool IsFocusable() const; + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // Overridden from AnimationDelegate to advance the hover state. + virtual void AnimationProgressed(const Animation* animation); + + // Returns a string containing the mnemonic, or the keyboard shortcut. + bool GetAccessibleKeyboardShortcut(std::wstring* shortcut); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Assigns a keyboard shortcut string description. + void SetAccessibleKeyboardShortcut(const std::wstring& shortcut); + + // Assigns an accessible string name. + void SetAccessibleName(const std::wstring& name); + + // + // These methods are overriden to implement a simple push button + // behavior + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + virtual bool OnMouseDragged(const ChromeViews::MouseEvent& e); + virtual void OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled); + virtual void OnMouseMoved(const ChromeViews::MouseEvent& e); + virtual void OnMouseEntered(const ChromeViews::MouseEvent& e); + virtual void OnMouseExited(const ChromeViews::MouseEvent& e); + virtual bool OnKeyPressed(const KeyEvent& e); + virtual bool OnKeyReleased(const KeyEvent& e); + + class ButtonListener { + public: + // + // This is invoked once the button is released use BaseButton::GetTag() + // to find out which button has been pressed. + // + virtual void ButtonPressed(BaseButton* sender) = 0; + }; + + // + // The the listener, the object that receives a notification when this + // button is pressed. tag is any int value to uniquely identify this + // button. + virtual void SetListener(ButtonListener *l, int tag); + + // + // Return the button tag as set by SetListener() + virtual int GetTag(); + + // + // Cause the button to notify the listener that a click occured. + virtual void NotifyClick(int mouse_event_flags); + + // Valid when the listener is notified. Contains the event flags from the + // mouse event, or 0 if not invoked from a mouse event. + int mouse_event_flags() { return mouse_event_flags_; } + + // + // Get the state. + // + int GetState() const { + return state_; + } + + virtual void Paint(ChromeCanvas* canvas); + + // Variant of paint that allows you to specify whether the paint is for a + // drag operation. This may be used during drag and drop to get a + // representation of this button suitable for drag and drop. + virtual void Paint(ChromeCanvas* canvas, bool for_drag); + + protected: + BaseButton(); + + // Returns true if the event is one that can trigger notifying the listener. + // This implementation returns true if the left mouse button is down. + virtual bool IsTriggerableEvent(const ChromeViews::MouseEvent& e); + + // + // Set the state. If the state is different, causes the button + // to be repainted + // + virtual void SetState(ButtonState new_state); + + virtual void OnDragDone(); + + // Overriden to reset the state to normal (as long as we're not disabled). + // This ensures we don't get stuck in a down state if on click our ancestor + // is removed. + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // tooltip text storage + std::wstring tooltip_text_; + + // storage of strings needed for accessibility + std::wstring accessible_shortcut_; + std::wstring accessible_name_; + + // The button state (defined in implementation) + int state_; + + // Hover animation. + scoped_ptr<ThrobAnimation> hover_animation_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(BaseButton); + + // The current listener + ButtonListener* listener_; + + // tag storage + int tag_; + + // See description in mouse_event_flags(). + int mouse_event_flags_; + + // Should we animate when the state changes? Defaults to true, but false while + // throbbing. + bool animate_on_state_change_; +}; + +} // namespace + +#endif // CHROME_VIEWS_BASE_BUTTON_H__ diff --git a/chrome/views/bitmap_scroll_bar.cc b/chrome/views/bitmap_scroll_bar.cc new file mode 100644 index 0000000..ac7f5f6 --- /dev/null +++ b/chrome/views/bitmap_scroll_bar.cc @@ -0,0 +1,749 @@ +// 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 "chrome/views/bitmap_scroll_bar.h" + +#include "base/message_loop.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/menu.h" +#include "chrome/views/scroll_view.h" +#include "skia/include/SkBitmap.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +namespace { + +// The distance the mouse can be dragged outside the bounds of the thumb during +// dragging before the scrollbar will snap back to its regular position. +static const int kScrollThumbDragOutSnap = 100; + +/////////////////////////////////////////////////////////////////////////////// +// +// AutorepeatButton +// +// A button that activates on mouse pressed rather than released, and that +// continues to fire the clicked action as the mouse button remains pressed +// down on the button. +// +/////////////////////////////////////////////////////////////////////////////// +class AutorepeatButton : public Button { + public: + AutorepeatButton() + : repeater_(NewCallback<AutorepeatButton>(this, + &AutorepeatButton::NotifyClick)) { + } + virtual ~AutorepeatButton() {} + + protected: + virtual bool OnMousePressed(const MouseEvent& event) { + Button::NotifyClick(event.GetFlags()); + repeater_.Start(); + return true; + } + + virtual void OnMouseReleased(const MouseEvent& event, bool canceled) { + repeater_.Stop(); + View::OnMouseReleased(event, canceled); + } + + private: + void NotifyClick() { + BaseButton::NotifyClick(0); + } + + // The repeat controller that we use to repeatedly click the button when the + // mouse button is down. + RepeatController repeater_; + + DISALLOW_EVIL_CONSTRUCTORS(AutorepeatButton); +}; + +/////////////////////////////////////////////////////////////////////////////// +// +// BitmapScrollBarThumb +// +// A view that acts as the thumb in the scroll bar track that the user can +// drag to scroll the associated contents view within the viewport. +// +/////////////////////////////////////////////////////////////////////////////// +class BitmapScrollBarThumb : public View { + public: + explicit BitmapScrollBarThumb(BitmapScrollBar* scroll_bar) + : scroll_bar_(scroll_bar), + drag_start_position_(-1), + mouse_offset_(-1), + state_(BaseButton::BS_NORMAL) { + } + virtual ~BitmapScrollBarThumb() { } + + // Sets the size (width or height) of the thumb to the specified value. + void SetSize(int size) { + // Make sure the thumb is never sized smaller than its minimum possible + // display size. + CSize prefsize; + GetPreferredSize(&prefsize); + size = std::max(size, + static_cast<int>(scroll_bar_->IsHorizontal() ? + prefsize.cx : prefsize.cy)); + CRect bounds; + GetBounds(&bounds); + if (scroll_bar_->IsHorizontal()) { + bounds.right = bounds.left + size; + } else { + bounds.bottom = bounds.top + size; + } + SetBounds(bounds); + } + + // Retrieves the size (width or height) of the thumb. + int GetSize() const { + CRect bounds; + GetBounds(&bounds); + if (scroll_bar_->IsHorizontal()) + return bounds.Width(); + return bounds.Height(); + } + + // Sets the position of the thumb on the x or y axis. + void SetPosition(int position) { + CRect bounds; + GetBounds(&bounds); + gfx::Rect track_bounds = scroll_bar_->GetTrackBounds(); + if (scroll_bar_->IsHorizontal()) { + bounds.MoveToX(track_bounds.x() + position); + } else { + bounds.MoveToY(track_bounds.y() + position); + } + SetBounds(bounds); + } + + // Gets the position of the thumb on the x or y axis. + int GetPosition() const { + CRect bounds; + GetBounds(&bounds); + gfx::Rect track_bounds = scroll_bar_->GetTrackBounds(); + if (scroll_bar_->IsHorizontal()) + return bounds.left - track_bounds.x(); + return bounds.top - track_bounds.y(); + } + + // View overrides: + virtual void GetPreferredSize(CSize* preferred_size) { + DCHECK(preferred_size); + preferred_size->cx = background_bitmap()->width(); + preferred_size->cy = start_cap_bitmap()->height() + + end_cap_bitmap()->height() + grippy_bitmap()->height(); + } + + protected: + // View overrides: + virtual void Paint(ChromeCanvas* canvas) { + canvas->DrawBitmapInt(*start_cap_bitmap(), 0, 0); + int top_cap_height = start_cap_bitmap()->height(); + int bottom_cap_height = end_cap_bitmap()->height(); + int thumb_body_height = GetHeight() - top_cap_height - bottom_cap_height; + canvas->TileImageInt(*background_bitmap(), 0, top_cap_height, + background_bitmap()->width(), thumb_body_height); + canvas->DrawBitmapInt(*end_cap_bitmap(), 0, + GetHeight() - bottom_cap_height); + + // Paint the grippy over the track. + int grippy_x = (GetWidth() - grippy_bitmap()->width()) / 2; + int grippy_y = (thumb_body_height - grippy_bitmap()->height()) / 2; + canvas->DrawBitmapInt(*grippy_bitmap(), grippy_x, grippy_y); + } + + virtual void OnMouseEntered(const MouseEvent& event) { + SetState(BaseButton::BS_HOT); + } + + virtual void OnMouseExited(const MouseEvent& event) { + SetState(BaseButton::BS_NORMAL); + } + + virtual bool OnMousePressed(const MouseEvent& event) { + mouse_offset_ = scroll_bar_->IsHorizontal() ? event.GetX() : event.GetY(); + drag_start_position_ = GetPosition(); + SetState(BaseButton::BS_PUSHED); + return true; + } + + virtual bool OnMouseDragged(const MouseEvent& event) { + // If the user moves the mouse more than |kScrollThumbDragOutSnap| outside + // the bounds of the thumb, the scrollbar will snap the scroll back to the + // point it was at before the drag began. + if (scroll_bar_->IsHorizontal()) { + if ((event.GetY() < GetY() - kScrollThumbDragOutSnap) || + (event.GetY() > (GetY() + GetHeight() + kScrollThumbDragOutSnap))) { + scroll_bar_->ScrollToThumbPosition(drag_start_position_, false); + return true; + } + } else { + if ((event.GetX() < GetX() - kScrollThumbDragOutSnap) || + (event.GetX() > (GetX() + GetWidth() + kScrollThumbDragOutSnap))) { + scroll_bar_->ScrollToThumbPosition(drag_start_position_, false); + return true; + } + } + if (scroll_bar_->IsHorizontal()) { + int thumb_x = event.GetX() - mouse_offset_; + scroll_bar_->ScrollToThumbPosition(GetX() + thumb_x, false); + } else { + int thumb_y = event.GetY() - mouse_offset_; + scroll_bar_->ScrollToThumbPosition(GetY() + thumb_y, false); + } + return true; + } + + virtual void OnMouseReleased(const MouseEvent& event, + bool canceled) { + SetState(BaseButton::BS_HOT); + View::OnMouseReleased(event, canceled); + } + + private: + // Returns the bitmap rendered at the start of the thumb. + SkBitmap* start_cap_bitmap() const { + return scroll_bar_->images_[BitmapScrollBar::THUMB_START_CAP][state_]; + } + + // Returns the bitmap rendered at the end of the thumb. + SkBitmap* end_cap_bitmap() const { + return scroll_bar_->images_[BitmapScrollBar::THUMB_END_CAP][state_]; + } + + // Returns the bitmap that is tiled in the background of the thumb between + // the start and the end caps. + SkBitmap* background_bitmap() const { + return scroll_bar_->images_[BitmapScrollBar::THUMB_MIDDLE][state_]; + } + + // Returns the bitmap that is rendered in the middle of the thumb + // transparently over the background bitmap. + SkBitmap* grippy_bitmap() const { + return scroll_bar_->images_[BitmapScrollBar::THUMB_GRIPPY][BaseButton::BS_NORMAL]; + } + + // Update our state and schedule a repaint when the mouse moves over us. + void SetState(BaseButton::ButtonState state) { + state_ = state; + SchedulePaint(); + } + + // The BitmapScrollBar that owns us. + BitmapScrollBar* scroll_bar_; + + int drag_start_position_; + + // The position of the mouse on the scroll axis relative to the top of this + // View when the drag started. + int mouse_offset_; + + // The current state of the thumb button. + BaseButton::ButtonState state_; + + DISALLOW_EVIL_CONSTRUCTORS(BitmapScrollBarThumb); +}; + +} // anonymous namespace + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, public: + +BitmapScrollBar::BitmapScrollBar(bool horizontal, bool show_scroll_buttons) + : contents_size_(0), + contents_scroll_offset_(0), + prev_button_(new AutorepeatButton), + next_button_(new AutorepeatButton), + thumb_(new BitmapScrollBarThumb(this)), + thumb_track_state_(BaseButton::BS_NORMAL), + last_scroll_amount_(SCROLL_NONE), + repeater_(NewCallback<BitmapScrollBar>(this, + &BitmapScrollBar::TrackClicked)), + context_menu_mouse_position_(0), + show_scroll_buttons_(show_scroll_buttons), + ScrollBar(horizontal) { + if (!show_scroll_buttons_) { + prev_button_->SetVisible(false); + next_button_->SetVisible(false); + } + prev_button_->SetListener(this, -1); + next_button_->SetListener(this, -1); + + AddChildView(prev_button_); + AddChildView(next_button_); + AddChildView(thumb_); + + SetContextMenuController(this); + prev_button_->SetContextMenuController(this); + next_button_->SetContextMenuController(this); + thumb_->SetContextMenuController(this); +} + +gfx::Rect BitmapScrollBar::GetTrackBounds() const { + CSize prefsize; + prev_button_->GetPreferredSize(&prefsize); + if (IsHorizontal()) { + if (!show_scroll_buttons_) + prefsize.cx = 0; + int new_width = std::max(0, + static_cast<int>(GetWidth() - (prefsize.cx * 2))); + gfx::Rect track_bounds(prefsize.cx, 0, new_width, prefsize.cy); + return track_bounds; + } + if (!show_scroll_buttons_) + prefsize.cy = 0; + gfx::Rect track_bounds(0, prefsize.cy, prefsize.cx, + std::max(0l, GetHeight() - (prefsize.cy * 2))); + return track_bounds; +} + +void BitmapScrollBar::SetImage(ScrollBarPart part, + BaseButton::ButtonState state, + SkBitmap* bitmap) { + DCHECK(part < PART_COUNT); + DCHECK(state < BaseButton::kButtonStateCount); + switch (part) { + case PREV_BUTTON: + prev_button_->SetImage(state, bitmap); + break; + case NEXT_BUTTON: + next_button_->SetImage(state, bitmap); + break; + case THUMB_START_CAP: + case THUMB_MIDDLE: + case THUMB_END_CAP: + case THUMB_GRIPPY: + case THUMB_TRACK: + images_[part][state] = bitmap; + break; + } +} + +void BitmapScrollBar::ScrollByAmount(ScrollAmount amount) { + ScrollBarController* controller = GetController(); + int offset = contents_scroll_offset_; + switch (amount) { + case SCROLL_START: + offset = GetMinPosition(); + break; + case SCROLL_END: + offset = GetMaxPosition(); + break; + case SCROLL_PREV_LINE: + offset -= controller->GetScrollIncrement(this, false, false); + offset = std::max(GetMinPosition(), offset); + break; + case SCROLL_NEXT_LINE: + offset += controller->GetScrollIncrement(this, false, true); + offset = std::min(GetMaxPosition(), offset); + break; + case SCROLL_PREV_PAGE: + offset -= controller->GetScrollIncrement(this, true, false); + offset = std::max(GetMinPosition(), offset); + break; + case SCROLL_NEXT_PAGE: + offset += controller->GetScrollIncrement(this, true, true); + offset = std::min(GetMaxPosition(), offset); + break; + } + contents_scroll_offset_ = offset; + ScrollContentsToOffset(); +} + +void BitmapScrollBar::ScrollToThumbPosition(int thumb_position, + bool scroll_to_middle) { + contents_scroll_offset_ = + CalculateContentsOffset(thumb_position, scroll_to_middle); + if (contents_scroll_offset_ < GetMinPosition()) { + contents_scroll_offset_ = GetMinPosition(); + } else if (contents_scroll_offset_ > GetMaxPosition()) { + contents_scroll_offset_ = GetMaxPosition(); + } + ScrollContentsToOffset(); + SchedulePaint(); +} + +void BitmapScrollBar::ScrollByContentsOffset(int contents_offset) { + contents_scroll_offset_ -= contents_offset; + if (contents_scroll_offset_ < GetMinPosition()) { + contents_scroll_offset_ = GetMinPosition(); + } else if (contents_scroll_offset_ > GetMaxPosition()) { + contents_scroll_offset_ = GetMaxPosition(); + } + ScrollContentsToOffset(); +} + +void BitmapScrollBar::TrackClicked() { + if (last_scroll_amount_ != SCROLL_NONE) { + CRect thumb_bounds; + thumb_->GetBounds(&thumb_bounds); + ScrollByAmount(last_scroll_amount_); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, View implementation: + +void BitmapScrollBar::GetPreferredSize(CSize* preferred_size) { + DCHECK(preferred_size); + + // In this case, we're returning the desired width of the scrollbar and its + // minimum allowable height. + CSize button_prefsize; + prev_button_->GetPreferredSize(&button_prefsize); + preferred_size->cx = button_prefsize.cx; + preferred_size->cy = button_prefsize.cy * 2; +} + +void BitmapScrollBar::Paint(ChromeCanvas* canvas) { + // Paint the track. + gfx::Rect track_bounds = GetTrackBounds(); + canvas->TileImageInt(*images_[THUMB_TRACK][thumb_track_state_], + track_bounds.x(), track_bounds.y(), + track_bounds.width(), track_bounds.height()); +} + +void BitmapScrollBar::Layout() { + // Size and place the two scroll buttons. + if (show_scroll_buttons_) { + CSize prefsize; + prev_button_->GetPreferredSize(&prefsize); + prev_button_->SetBounds(0, 0, prefsize.cx, prefsize.cy); + next_button_->GetPreferredSize(&prefsize); + if (IsHorizontal()) { + next_button_->SetBounds(GetWidth() - prefsize.cx, 0, prefsize.cx, + prefsize.cy); + } else { + next_button_->SetBounds(0, GetHeight() - prefsize.cy, prefsize.cx, + prefsize.cy); + } + } else { + prev_button_->SetBounds(0, 0, 0, 0); + next_button_->SetBounds(0, 0, 0, 0); + } + + // Size and place the thumb + CSize thumb_prefsize; + thumb_->GetPreferredSize(&thumb_prefsize); + gfx::Rect track_bounds = GetTrackBounds(); + + // Preserve the height/width of the thumb (depending on orientation) as set + // by the last call to |Update|, but coerce the width/height to be the + // appropriate value for the bitmaps provided. + CRect bounds; + thumb_->GetBounds(&bounds); + if (IsHorizontal()) { + thumb_->SetBounds(bounds.left, bounds.top, bounds.Width(), + thumb_prefsize.cy); + } else { + thumb_->SetBounds(bounds.left, bounds.top, thumb_prefsize.cx, + bounds.Height()); + } + + // Hide the thumb if the track isn't tall enough to display even a tiny + // thumb. The user can only use the mousewheel, scroll buttons or keyboard + // in this scenario. + if ((IsHorizontal() && (track_bounds.width() < thumb_prefsize.cx)) || + (!IsHorizontal() && (track_bounds.height() < thumb_prefsize.cy))) { + thumb_->SetVisible(false); + } else if (!thumb_->IsVisible()) { + thumb_->SetVisible(true); + } +} + +void BitmapScrollBar::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +bool BitmapScrollBar::OnMousePressed(const MouseEvent& event) { + if (event.IsOnlyLeftMouseButton()) { + SetThumbTrackState(BaseButton::BS_PUSHED); + CRect thumb_bounds; + thumb_->GetBounds(&thumb_bounds); + if (IsHorizontal()) { + if (event.GetX() < thumb_bounds.left) { + last_scroll_amount_ = SCROLL_PREV_PAGE; + } else if (event.GetX() > thumb_bounds.right) { + last_scroll_amount_ = SCROLL_NEXT_PAGE; + } + } else { + if (event.GetY() < thumb_bounds.top) { + last_scroll_amount_ = SCROLL_PREV_PAGE; + } else if (event.GetY() > thumb_bounds.bottom) { + last_scroll_amount_ = SCROLL_NEXT_PAGE; + } + } + TrackClicked(); + repeater_.Start(); + } + return true; +} + +void BitmapScrollBar::OnMouseReleased(const MouseEvent& event, bool canceled) { + SetThumbTrackState(BaseButton::BS_NORMAL); + repeater_.Stop(); + View::OnMouseReleased(event, canceled); +} + +bool BitmapScrollBar::OnMouseWheel(const MouseWheelEvent& event) { + ScrollByContentsOffset(event.GetOffset()); + return true; +} + +bool BitmapScrollBar::OnKeyPressed(const KeyEvent& event) { + ScrollAmount amount = SCROLL_NONE; + switch(event.GetCharacter()) { + case VK_UP: + if (!IsHorizontal()) + amount = SCROLL_PREV_LINE; + break; + case VK_DOWN: + if (!IsHorizontal()) + amount = SCROLL_NEXT_LINE; + break; + case VK_LEFT: + if (IsHorizontal()) + amount = SCROLL_PREV_LINE; + break; + case VK_RIGHT: + if (IsHorizontal()) + amount = SCROLL_NEXT_LINE; + break; + case VK_PRIOR: + amount = SCROLL_PREV_PAGE; + break; + case VK_NEXT: + amount = SCROLL_NEXT_PAGE; + break; + case VK_HOME: + amount = SCROLL_START; + break; + case VK_END: + amount = SCROLL_END; + break; + } + if (amount != SCROLL_NONE) { + ScrollByAmount(amount); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, ContextMenuController implementation: + +enum ScrollBarContextMenuCommands { + ScrollBarContextMenuCommand_ScrollHere = 1, + ScrollBarContextMenuCommand_ScrollStart, + ScrollBarContextMenuCommand_ScrollEnd, + ScrollBarContextMenuCommand_ScrollPageUp, + ScrollBarContextMenuCommand_ScrollPageDown, + ScrollBarContextMenuCommand_ScrollPrev, + ScrollBarContextMenuCommand_ScrollNext +}; + +void BitmapScrollBar::ShowContextMenu(View* source, int x, int y, + bool is_mouse_gesture) { + ViewContainer* vc = GetViewContainer(); + CRect vc_bounds; + vc->GetBounds(&vc_bounds, true); + CPoint temp_pt(x - vc_bounds.left, y - vc_bounds.top); + View::ConvertPointFromViewContainer(this, &temp_pt); + context_menu_mouse_position_ = IsHorizontal() ? temp_pt.x : temp_pt.y; + + Menu menu(this, Menu::TOPLEFT, GetViewContainer()->GetHWND()); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollHere); + menu.AppendSeparator(); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollStart); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollEnd); + menu.AppendSeparator(); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPageUp); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPageDown); + menu.AppendSeparator(); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPrev); + menu.AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollNext); + menu.RunMenuAt(x, y); +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, Menu::Delegate implementation: + +std::wstring BitmapScrollBar::GetLabel(int id) const { + switch (id) { + case ScrollBarContextMenuCommand_ScrollHere: + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLHERE); + case ScrollBarContextMenuCommand_ScrollStart: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLLEFTEDGE); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLHOME); + case ScrollBarContextMenuCommand_ScrollEnd: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLRIGHTEDGE); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLEND); + case ScrollBarContextMenuCommand_ScrollPageUp: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLPAGEUP); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLPAGEUP); + case ScrollBarContextMenuCommand_ScrollPageDown: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLPAGEDOWN); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLPAGEDOWN); + case ScrollBarContextMenuCommand_ScrollPrev: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLLEFT); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLUP); + case ScrollBarContextMenuCommand_ScrollNext: + if (IsHorizontal()) + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLRIGHT); + return l10n_util::GetString(IDS_SCROLLBAR_CXMENU_SCROLLDOWN); + } + NOTREACHED() << "Invalid BitmapScrollBar Context Menu command!"; + return L""; +} + +bool BitmapScrollBar::IsCommandEnabled(int id) const { + switch (id) { + case ScrollBarContextMenuCommand_ScrollPageUp: + case ScrollBarContextMenuCommand_ScrollPageDown: + return !IsHorizontal(); + } + return true; +} + +void BitmapScrollBar::ExecuteCommand(int id) { + switch (id) { + case ScrollBarContextMenuCommand_ScrollHere: + ScrollToThumbPosition(context_menu_mouse_position_, true); + break; + case ScrollBarContextMenuCommand_ScrollStart: + ScrollByAmount(SCROLL_START); + break; + case ScrollBarContextMenuCommand_ScrollEnd: + ScrollByAmount(SCROLL_END); + break; + case ScrollBarContextMenuCommand_ScrollPageUp: + ScrollByAmount(SCROLL_PREV_PAGE); + break; + case ScrollBarContextMenuCommand_ScrollPageDown: + ScrollByAmount(SCROLL_NEXT_PAGE); + break; + case ScrollBarContextMenuCommand_ScrollPrev: + ScrollByAmount(SCROLL_PREV_LINE); + break; + case ScrollBarContextMenuCommand_ScrollNext: + ScrollByAmount(SCROLL_NEXT_LINE); + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, BaseButton::ButtonListener implementation: + +void BitmapScrollBar::ButtonPressed(BaseButton* sender) { + if (sender == prev_button_) { + ScrollByAmount(SCROLL_PREV_LINE); + } else if (sender == next_button_) { + ScrollByAmount(SCROLL_NEXT_LINE); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, ScrollBar implementation: + +void BitmapScrollBar::Update(int viewport_size, int content_size, + int contents_scroll_offset) { + ScrollBar::Update(viewport_size, content_size, contents_scroll_offset); + + // Make sure contents_size is always > 0 to avoid divide by zero errors in + // calculations throughout this code. + contents_size_ = std::max(1, content_size); + + if (content_size < 0) + content_size = 0; + if (contents_scroll_offset < 0) + contents_scroll_offset = 0; + if (contents_scroll_offset > content_size) + contents_scroll_offset = content_size; + + // Thumb Height and Thumb Pos. + // The height of the thumb is the ratio of the Viewport height to the + // content size multiplied by the height of the thumb track. + double ratio = static_cast<double>(viewport_size) / contents_size_; + int thumb_size = static_cast<int>(ratio * GetTrackSize()); + thumb_->SetSize(thumb_size); + + int thumb_position = CalculateThumbPosition(contents_scroll_offset); + thumb_->SetPosition(thumb_position); +} + +int BitmapScrollBar::GetLayoutSize() const { + CSize prefsize; + prev_button_->GetPreferredSize(&prefsize); + return IsHorizontal() ? prefsize.cy : prefsize.cx; +} + +int BitmapScrollBar::GetPosition() const { + return thumb_->GetPosition(); +} + +/////////////////////////////////////////////////////////////////////////////// +// BitmapScrollBar, private: + +void BitmapScrollBar::ScrollContentsToOffset() { + GetController()->ScrollToPosition(this, contents_scroll_offset_); + thumb_->SetPosition(CalculateThumbPosition(contents_scroll_offset_)); +} + +int BitmapScrollBar::GetTrackSize() const { + gfx::Rect track_bounds = GetTrackBounds(); + return IsHorizontal() ? track_bounds.width() : track_bounds.height(); +} + +int BitmapScrollBar::CalculateThumbPosition(int contents_scroll_offset) const { + return (contents_scroll_offset * GetTrackSize()) / contents_size_; +} + +int BitmapScrollBar::CalculateContentsOffset(int thumb_position, + bool scroll_to_middle) const { + if (scroll_to_middle) + thumb_position = thumb_position - (thumb_->GetSize() / 2); + return (thumb_position * contents_size_) / GetTrackSize(); +} + +void BitmapScrollBar::SetThumbTrackState(BaseButton::ButtonState state) { + thumb_track_state_ = state; + SchedulePaint(); +} + +} diff --git a/chrome/views/bitmap_scroll_bar.h b/chrome/views/bitmap_scroll_bar.h new file mode 100644 index 0000000..f5e7610 --- /dev/null +++ b/chrome/views/bitmap_scroll_bar.h @@ -0,0 +1,209 @@ +// 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. + +#ifndef CHROME_VIEWS_BITMAP_SCROLL_BAR_H__ +#define CHROME_VIEWS_BITMAP_SCROLL_BAR_H__ + +#include "chrome/views/button.h" +#include "chrome/views/menu.h" +#include "chrome/views/repeat_controller.h" +#include "chrome/views/scroll_bar.h" + +namespace ChromeViews { + +namespace { +class BitmapScrollBarThumb; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// BitmapScrollBar +// +// A ScrollBar subclass that implements a scroll bar rendered using bitmaps +// that the user provides. There are bitmaps for the up and down buttons, as +// well as for the thumb and track. This is intended for creating UIs that +// have customized, non-native appearances, like floating HUDs etc. +// +// Maybe TODO(beng): (Cleanup) If we need to, we may want to factor rendering +// out of this altogether and have the user supply +// Background impls for each component, and just use those +// to render, so that for example we get native theme +// rendering. +// +/////////////////////////////////////////////////////////////////////////////// +class BitmapScrollBar : public ScrollBar, + public BaseButton::ButtonListener, + public ContextMenuController, + public Menu::Delegate { + public: + BitmapScrollBar(bool horizontal, bool show_scroll_buttons); + virtual ~BitmapScrollBar() { } + + // Get the bounds of the "track" area that the thumb is free to slide within. + gfx::Rect GetTrackBounds() const; + + // A list of parts that the user may supply bitmaps for. + enum ScrollBarPart { + PREV_BUTTON = 0, // The button used to represent scrolling up/left by 1 line. + NEXT_BUTTON, // The button used to represent scrolling down/right by 1 line. + // IMPORTANT: The code assumes the prev and next + // buttons have equal width and equal height. + THUMB_START_CAP, // The top/left segment of the thumb on the scrollbar. + THUMB_MIDDLE, // The tiled background image of the thumb. + THUMB_END_CAP, // The bottom/right segment of the thumb on the scrollbar. + THUMB_GRIPPY, // The grippy that is rendered in the center of the thumb. + THUMB_TRACK, // The tiled background image of the thumb track. + PART_COUNT + }; + + // Sets the bitmap to be rendered for the specified part and state. + void SetImage(ScrollBarPart part, + BaseButton::ButtonState state, + SkBitmap* bitmap); + + // An enumeration of different amounts of incremental scroll, representing + // events sent from different parts of the UI/keyboard. + enum ScrollAmount { + SCROLL_NONE = 0, + SCROLL_START, + SCROLL_END, + SCROLL_PREV_LINE, + SCROLL_NEXT_LINE, + SCROLL_PREV_PAGE, + SCROLL_NEXT_PAGE, + }; + + // Scroll the contents by the specified type (see ScrollAmount above). + void ScrollByAmount(ScrollAmount amount); + + // Scroll the contents to the appropriate position given the supplied + // position of the thumb (thumb track coordinates). If |scroll_to_middle| is + // true, then the conversion assumes |thumb_position| is in the middle of the + // thumb rather than the top. + void ScrollToThumbPosition(int thumb_position, bool scroll_to_middle); + + // Scroll the contents by the specified offset (contents coordinates). + void ScrollByContentsOffset(int contents_offset); + + // View overrides: + virtual void GetPreferredSize(CSize* preferred_size); + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual bool OnMousePressed(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual bool OnMouseWheel(const MouseWheelEvent& event); + virtual bool OnKeyPressed(const KeyEvent& event); + + // BaseButton::ButtonListener overrides: + virtual void ButtonPressed(BaseButton* sender); + + // ScrollBar overrides: + virtual void Update(int viewport_size, + int content_size, + int contents_scroll_offset); + virtual int GetLayoutSize() const; + virtual int GetPosition() const; + + // ContextMenuController overrides. + virtual void ShowContextMenu(View* source, int x, int y, + bool is_mouse_gesture); + + // Menu::Delegate overrides: + virtual std::wstring GetLabel(int id) const; + virtual bool IsCommandEnabled(int id) const; + virtual void ExecuteCommand(int id); + + private: + // Called when the mouse is pressed down in the track area. + void TrackClicked(); + + // Responsible for scrolling the contents and also updating the UI to the + // current value of the Scroll Offset. + void ScrollContentsToOffset(); + + // Returns the size (width or height) of the track area of the ScrollBar. + int GetTrackSize() const; + + // Calculate the position of the thumb within the track based on the + // specified scroll offset of the contents. + int CalculateThumbPosition(int contents_scroll_offset) const; + + // Calculates the current value of the contents offset (contents coordinates) + // based on the current thumb position (thumb track coordinates). See + // |ScrollToThumbPosition| for an explanation of |scroll_to_middle|. + int CalculateContentsOffset(int thumb_position, + bool scroll_to_middle) const; + + // Called when the state of the thumb track changes (e.g. by the user + // pressing the mouse button down in it). + void SetThumbTrackState(BaseButton::ButtonState state); + + // The thumb needs to be able to access the part images. + friend BitmapScrollBarThumb; + SkBitmap* images_[PART_COUNT][BaseButton::kButtonStateCount]; + + // The size of the scrolled contents, in pixels. + int contents_size_; + + // The current amount the contents is offset by in the viewport. + int contents_scroll_offset_; + + // Up/Down/Left/Right buttons and the Thumb. + Button* prev_button_; + Button* next_button_; + BitmapScrollBarThumb* thumb_; + + // The state of the scrollbar track. Typically, the track will highlight when + // the user presses the mouse on them (during page scrolling). + BaseButton::ButtonState thumb_track_state_; + + // The last amount of incremental scroll that this scrollbar performed. This + // is accessed by the callbacks for the auto-repeat up/down buttons to know + // what direction to repeatedly scroll in. + ScrollAmount last_scroll_amount_; + + // An instance of a RepeatController which scrolls the scrollbar continuously + // as the user presses the mouse button down on the up/down buttons or the + // track. + RepeatController repeater_; + + // The position of the mouse within the scroll bar when the context menu + // was invoked. + int context_menu_mouse_position_; + + // True if the scroll buttons at each end of the scroll bar should be shown. + bool show_scroll_buttons_; + + DISALLOW_EVIL_CONSTRUCTORS(BitmapScrollBar); +}; + +} + +#endif // #ifndef CHROME_VIEWS_BITMAP_SCROLL_BAR_H__ diff --git a/chrome/views/border.cc b/chrome/views/border.cc new file mode 100644 index 0000000..3904a508 --- /dev/null +++ b/chrome/views/border.cc @@ -0,0 +1,129 @@ +// 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 "border.h" + +namespace ChromeViews { + + +namespace { + +// A simple border with a fixed thickness and single color. +class SolidBorder : public Border { + public: + SolidBorder(int thickness, SkColor color); + + virtual void Paint(const View& view, ChromeCanvas* canvas) const; + virtual void GetInsets(gfx::Insets* insets) const; + + private: + int thickness_; + SkColor color_; + gfx::Insets insets_; + + DISALLOW_EVIL_CONSTRUCTORS(SolidBorder); +}; + +SolidBorder::SolidBorder(int thickness, SkColor color) + : thickness_(thickness), + color_(color), + insets_(thickness, thickness, thickness, thickness) { +} + +void SolidBorder::Paint(const View& view, ChromeCanvas* canvas) const { + gfx::Rect clip_rect; + if (!canvas->GetClipRect(&clip_rect)) + return; // Empty clip rectangle, nothing to paint. + + // Top border. + gfx::Rect border_bounds(0, 0, view.GetWidth(), insets_.top()); + if (clip_rect.Intersects(border_bounds)) + canvas->FillRectInt(color_, 0, 0, view.GetWidth(), insets_.top()); + // Left border. + border_bounds.SetRect(0, 0, insets_.left(), view.GetHeight()); + if (clip_rect.Intersects(border_bounds)) + canvas->FillRectInt(color_, 0, 0, insets_.left(), view.GetHeight()); + // Bottom border. + border_bounds.SetRect(0, view.GetHeight() - insets_.bottom(), + view.GetWidth(), insets_.bottom()); + if (clip_rect.Intersects(border_bounds)) + canvas->FillRectInt(color_, 0, view.GetHeight() - insets_.bottom(), + view.GetWidth(), insets_.bottom()); + // Right border. + border_bounds.SetRect(view.GetWidth() - insets_.right(), 0, + insets_.right(), view.GetHeight()); + if (clip_rect.Intersects(border_bounds)) + canvas->FillRectInt(color_, view.GetWidth() - insets_.right(), 0, + insets_.right(), view.GetHeight()); +} + +void SolidBorder::GetInsets(gfx::Insets* insets) const { + DCHECK(insets); + insets->Set(insets_.top(), insets_.left(), insets_.bottom(), insets_.right()); +} + +class EmptyBorder : public Border { + public: + EmptyBorder(int top, int left, int bottom, int right) + : top_(top), left_(left), bottom_(bottom), right_(right) {} + + virtual void Paint(const View& view, ChromeCanvas* canvas) const {} + + virtual void GetInsets(gfx::Insets* insets) const { + DCHECK(insets); + insets->Set(top_, left_, bottom_, right_); + } + + private: + int top_; + int left_; + int bottom_; + int right_; + + DISALLOW_EVIL_CONSTRUCTORS(EmptyBorder); +}; +} + +Border::Border() { +} + +Border::~Border() { +} + +// static +Border* Border::CreateSolidBorder(int thickness, SkColor color) { + return new SolidBorder(thickness, color); +} + +// static +Border* Border::CreateEmptyBorder(int top, int left, int bottom, int right) { + return new EmptyBorder(top, left, bottom, right); +} + +} // namespace diff --git a/chrome/views/border.h b/chrome/views/border.h new file mode 100644 index 0000000..ec6599d --- /dev/null +++ b/chrome/views/border.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef CHROME_VIEWS_BORDER_H__ +#define CHROME_VIEWS_BORDER_H__ + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/insets.h" +#include "chrome/views/view.h" +#include "SkColor.h" + +namespace ChromeViews { + + +class View; + +//////////////////////////////////////////////////////////////////////////////// +// +// Border class. +// +// The border class is used to display a border around a view. +// To set a border on a view, just call SetBorder on the view, for example: +// view->SetBorder(Border::CreateSolidBorder(1, SkColorSetRGB(25, 25, 112)); +// Once set on a view, the border is owned by the view. +// +// IMPORTANT NOTE: not all views support borders at this point. In order to +// support the border, views should make sure to use bounds excluding the +// border (by calling View::GetLocalBoundsExcludingBorder) when doing layout and +// painting. +// +//////////////////////////////////////////////////////////////////////////////// + +class Border { + public: + Border(); + virtual ~Border(); + + // Creates a border that is a simple line of the specified thickness and + // color. + static Border* CreateSolidBorder(int thickness, SkColor color); + + // Creates a border for reserving space. The returned border does not + // paint anything. + static Border* CreateEmptyBorder(int top, int left, int bottom, int right); + + // Renders the border for the specified view. + virtual void Paint(const View& view, ChromeCanvas* canvas) const = 0; + + // Sets the specified insets to the the border insets. + virtual void GetInsets(gfx::Insets* insets) const = 0; + + private: + DISALLOW_EVIL_CONSTRUCTORS(Border); +}; + +} + +#endif // CHROME_VIEWS_BORDER_H__ diff --git a/chrome/views/button.cc b/chrome/views/button.cc new file mode 100644 index 0000000..adc0647 --- /dev/null +++ b/chrome/views/button.cc @@ -0,0 +1,230 @@ +// 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 "chrome/views/button.h" + +#include <atlbase.h> +#include <atlapp.h> + +#include "base/gfx/image_operations.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/event.h" +#include "chrome/views/view_container.h" +#include "chrome/app/chrome_dll_resource.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +static const int kDefaultWidth = 16; // Default button width if no theme. +static const int kDefaultHeight = 14; // Default button height if no theme. + +//////////////////////////////////////////////////////////////////////////////// +// +// Button - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +Button::Button() : BaseButton(), + h_alignment_(ALIGN_LEFT), + v_alignment_(ALIGN_TOP) { + // By default, we request that the ChromeCanvas passed to our View::Paint() + // implementation is flipped horizontally so that the button's bitmaps are + // mirrored when the UI directionality is right-to-left. + EnableCanvasFlippingForRTLUI(true); +} + +Button::~Button() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Button - properties +// +//////////////////////////////////////////////////////////////////////////////// + +void Button::SetImage(ButtonState aState, SkBitmap* anImage) { + images_[aState] = anImage ? *anImage : SkBitmap(); +} + +void Button::SetImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align) { + h_alignment_ = h_align; + v_alignment_ = v_align; + SchedulePaint(); +} + +void Button::GetPreferredSize(CSize *result) { + if (!images_[BS_NORMAL].isNull()) { + result->cx = images_[BS_NORMAL].width(); + result->cy = images_[BS_NORMAL].height(); + } else { + result->cx = kDefaultWidth; + result->cy = kDefaultHeight; + } +} + +// Set the tooltip text for this button. +void Button::SetTooltipText(const std::wstring& text) { + tooltip_text_ = text; + TooltipTextChanged(); +} + +// Return the tooltip text currently used by this button. +std::wstring Button::GetTooltipText() const { + return tooltip_text_; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Button - painting +// +//////////////////////////////////////////////////////////////////////////////// + +void Button::Paint(ChromeCanvas* canvas) { + View::Paint(canvas); + SkBitmap img = GetImageToPaint(); + + if (!img.isNull()) { + int x = 0, y = 0; + + if (h_alignment_ == ALIGN_CENTER) + x = (GetWidth() - img.width()) / 2; + else if (h_alignment_ == ALIGN_RIGHT) + x = GetWidth() - img.width(); + + if (v_alignment_ == ALIGN_MIDDLE) + y = (GetHeight() - img.height()) / 2; + else if (v_alignment_ == ALIGN_BOTTOM) + y = GetHeight() - img.height(); + + canvas->DrawBitmapInt(img, x, y); + } + PaintFocusBorder(canvas); +} + +SkBitmap Button::GetImageToPaint() { + SkBitmap img; + + if (!images_[BS_HOT].isNull() && hover_animation_->IsAnimating()) { + img = gfx::ImageOperations::CreateBlendedBitmap(images_[BS_NORMAL], + images_[BS_HOT], hover_animation_->GetCurrentValue()); + } else { + img = images_[GetState()]; + } + + return !img.isNull() ? img : images_[BS_NORMAL]; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Button - accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool Button::GetAccessibleDefaultAction(std::wstring* action) { + DCHECK(action); + + action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS)); + return true; +} + +bool Button::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_PUSHBUTTON; + return true; +} + +bool Button::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (tooltip_text_.empty()) + return false; + + *tooltip = tooltip_text_; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ToggleButton +// +//////////////////////////////////////////////////////////////////////////////// +ToggleButton::ToggleButton() : Button(), toggled_(false) { +} + +ToggleButton::~ToggleButton() { +} + +void ToggleButton::SetImage(ButtonState state, SkBitmap* image) { + if (toggled_) { + alternate_images_[state] = image ? *image : SkBitmap(); + } else { + images_[state] = image ? *image : SkBitmap(); + if (state_ == state) + SchedulePaint(); + } +} + +void ToggleButton::SetToggledImage(ButtonState state, SkBitmap* image) { + if (toggled_) { + images_[state] = image ? *image : SkBitmap(); + if (state_ == state) + SchedulePaint(); + } else { + alternate_images_[state] = image ? *image : SkBitmap(); + } +} + +bool ToggleButton::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (!toggled_ || toggled_tooltip_text_.empty()) + return Button::GetTooltipText(x, y, tooltip); + + *tooltip = toggled_tooltip_text_; + return true; +} + +void ToggleButton::SetToggled(bool toggled) { + if (toggled == toggled_) + return; + + for (int i = 0; i < kButtonStateCount; ++i) { + SkBitmap temp = images_[i]; + images_[i] = alternate_images_[i]; + alternate_images_[i] = temp; + } + toggled_ = toggled; + SchedulePaint(); +} + +void ToggleButton::SetToggledTooltipText(const std::wstring& tooltip) { + toggled_tooltip_text_.assign(tooltip); +} +} // namespace ChromeViews diff --git a/chrome/views/button.h b/chrome/views/button.h new file mode 100644 index 0000000..9840ea3 --- /dev/null +++ b/chrome/views/button.h @@ -0,0 +1,160 @@ +// 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. + +#ifndef CHROME_VIEWS_BUTTON_H_ +#define CHROME_VIEWS_BUTTON_H_ + +#include <windows.h> + +#include "chrome/views/base_button.h" +#include "skia/include/SkBitmap.h" + +namespace ChromeViews { + +class MouseEvent; + +//////////////////////////////////////////////////////////////////////////////// +// +// Button +// +// A simple button class +// +//////////////////////////////////////////////////////////////////////////////// +class Button : public BaseButton { + public: + // + // Create a Button + Button(); + virtual ~Button(); + + // + // Set the image the button should use for the provided state. + virtual void SetImage(ButtonState aState, SkBitmap* anImage); + + enum HorizontalAlignment { ALIGN_LEFT = 0, + ALIGN_CENTER, + ALIGN_RIGHT, }; + + enum VerticalAlignment {ALIGN_TOP = 0, + ALIGN_MIDDLE, + ALIGN_BOTTOM }; + + void SetImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align); + + // + // Computes the minimum size given the current theme and graphics + void GetPreferredSize(CSize *result); + + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. + bool GetAccessibleDefaultAction(std::wstring* action); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Set the tooltip text for this button. + void SetTooltipText(const std::wstring& text); + + // Return the tooltip text currently used by this button. + std::wstring GetTooltipText() const; + + // Overridden from View. + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + protected: + + // Overridden to render the button. + virtual void Paint(ChromeCanvas* canvas); + + // Returns the image to paint. This is invoked from paint and returns a value + // from images. + virtual SkBitmap GetImageToPaint(); + + // Images. + SkBitmap images_[kButtonStateCount]; + + // Alignment State. + HorizontalAlignment h_alignment_; + VerticalAlignment v_alignment_; + + // The tooltip text or empty string for none. + std::wstring tooltip_text_; + + DISALLOW_EVIL_CONSTRUCTORS(Button); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// ToggleButton +// +// A togglable button. It swaps out its graphics when toggled. +// +//////////////////////////////////////////////////////////////////////////////// +class ToggleButton : public Button { + public: + ToggleButton(); + virtual ~ToggleButton(); + + // Overridden from Button. + virtual void SetImage(ButtonState aState, SkBitmap* anImage); + + // Like Button::SetImage(), but to set the graphics used for the + // "has been toggled" state. Must be called for each button state + // before the button is toggled. + void SetToggledImage(ButtonState state, SkBitmap* image); + + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + // Set the tooltip text displayed when the button is toggled. + void SetToggledTooltipText(const std::wstring& tooltip); + + // Change the toggled state. + void SetToggled(bool toggled); + + private: + // The parent class's images_ member is used for the current images, + // and this array is used to hold the alternative images. + // We swap between the two when toggling. + SkBitmap alternate_images_[kButtonStateCount]; + + bool toggled_; + + // The parent class's tooltip_text_ is displayed when not toggled, and + // this one is shown when toggled. + std::wstring toggled_tooltip_text_; + + DISALLOW_EVIL_CONSTRUCTORS(ToggleButton); +}; + +} // namespace + +#endif // CHROME_VIEWS_BUTTON_H_ diff --git a/chrome/views/button_dropdown.cc b/chrome/views/button_dropdown.cc new file mode 100644 index 0000000..dfcd3da --- /dev/null +++ b/chrome/views/button_dropdown.cc @@ -0,0 +1,204 @@ +// 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 "chrome/views/button_dropdown.h" + +#include "base/message_loop.h" +#include "chrome/browser/back_forward_menu_model.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/view_menu_delegate.h" +#include "chrome/views/view_container.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +// How long to wait before showing the menu +static const int kMenuTimerDelay = 500; + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - constructors, destructors, initialization, cleanup +// +//////////////////////////////////////////////////////////////////////////////// + +ButtonDropDown::ButtonDropDown(Menu::Delegate* menu_delegate) + : Button(), + menu_delegate_(menu_delegate), + y_position_on_lbuttondown_(0), + show_menu_factory_(this) { +} + +ButtonDropDown::~ButtonDropDown() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Events +// +//////////////////////////////////////////////////////////////////////////////// + +bool ButtonDropDown::OnMousePressed(const ChromeViews::MouseEvent& e) { + if (IsEnabled() && e.IsLeftMouseButton() && HitTest(e.GetLocation())) { + // Store the y pos of the mouse coordinates so we can use them later to + // determine if the user dragged the mouse down (which should pop up the + // drag down menu immediately, instead of waiting for the timer) + y_position_on_lbuttondown_ = e.GetY(); + + // Schedule a task that will show the menu. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + show_menu_factory_.NewRunnableMethod(&ButtonDropDown::ShowDropDownMenu, + GetViewContainer()->GetHWND()), + kMenuTimerDelay); + } + + return Button::OnMousePressed(e); +} + +void ButtonDropDown::OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled) { + Button::OnMouseReleased(e, canceled); + + if (canceled) + return; + + if (e.IsLeftMouseButton()) + show_menu_factory_.RevokeAll(); + + if (IsEnabled() && e.IsRightMouseButton() && HitTest(e.GetLocation())) { + show_menu_factory_.RevokeAll(); + // Make the button look depressed while the menu is open. + // NOTE: SetState() schedules a paint, but it won't occur until after the + // context menu message loop has terminated, so we PaintNow() to + // update the appearance synchronously. + SetState(BS_PUSHED); + PaintNow(); + ShowDropDownMenu(GetViewContainer()->GetHWND()); + } +} + +bool ButtonDropDown::OnMouseDragged(const ChromeViews::MouseEvent& e) { + bool result = Button::OnMouseDragged(e); + + if (!show_menu_factory_.empty()) { + // SM_CYDRAG is a pixel value for minimum dragging distance before operation + // counts as a drag, and not just as a click and accidental move of a mouse. + // See http://msdn2.microsoft.com/en-us/library/ms724385.aspx for details. + int dragging_threshold = GetSystemMetrics(SM_CYDRAG); + + // If the mouse is dragged to a y position lower than where it was when + // clicked then we should not wait for the menu to appear but show + // it immediately. + if (e.GetY() > y_position_on_lbuttondown_ + dragging_threshold) { + show_menu_factory_.RevokeAll(); + ShowDropDownMenu(GetViewContainer()->GetHWND()); + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Menu functions +// +//////////////////////////////////////////////////////////////////////////////// + +void ButtonDropDown::ShowDropDownMenu(HWND window) { + if (menu_delegate_) { + CRect lb; + GetLocalBounds(&lb, true); + + // Both the menu position and the menu anchor type change if the UI layout + // is right-to-left. + CPoint menu_position = CPoint(lb.TopLeft()); + menu_position.Offset(0, lb.Height() - 1); + if (UILayoutIsRightToLeft()) + menu_position.Offset(lb.Width() - 1, 0); + + Menu::AnchorPoint anchor = Menu::TOPLEFT; + if (UILayoutIsRightToLeft()) + anchor = Menu::TOPRIGHT; + + View::ConvertPointToScreen(this, &menu_position); + Menu menu(menu_delegate_, anchor, window); + + // ID's for AppendMenu is 1-based because RunMenu will ignore the user + // selection if id=0 is selected (0 = NO-OP) so we add 1 here and subtract 1 + // in the handlers above to get the actual index + int item_count = menu_delegate_->GetItemCount(); + for (int i = 0; i < item_count; i++) { + if (menu_delegate_->IsItemSeparator(i + 1)) { + menu.AppendSeparator(); + } else { + if (menu_delegate_->HasIcon(i + 1)) + menu.AppendMenuItemWithIcon(i + 1, L"", SkBitmap()); + else + menu.AppendMenuItem(i+1, L"", Menu::NORMAL); + } + } + + menu.RunMenuAt(menu_position.x, menu_position.y); + + // Need to explicitly clear mouse handler so that events get sent + // properly after the menu finishes running. If we don't do this, then + // the first click to other parts of the UI is eaten. + SetMouseHandler(NULL); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool ButtonDropDown::GetAccessibleDefaultAction(std::wstring* action) { + DCHECK(action); + + action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS)); + return true; +} + +bool ButtonDropDown::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN; + return true; +} + +bool ButtonDropDown::GetAccessibleState(VARIANT* state) { + DCHECK(state); + + state->lVal |= STATE_SYSTEM_HASPOPUP; + return true; +} + +} // namespace ChromeViews diff --git a/chrome/views/button_dropdown.h b/chrome/views/button_dropdown.h new file mode 100644 index 0000000..053df4e --- /dev/null +++ b/chrome/views/button_dropdown.h @@ -0,0 +1,93 @@ +// 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. + +#ifndef CHROME_VIEWS_BUTTON_DROPDOWN_H__ +#define CHROME_VIEWS_BUTTON_DROPDOWN_H__ + +#include "base/task.h" +#include "chrome/views/button.h" +#include "chrome/views/menu.h" + +class Timer; + +namespace ChromeViews { + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown +// +// A button class that when pressed (and held) or pressed (and drag down) will +// display a menu +// +//////////////////////////////////////////////////////////////////////////////// +class ButtonDropDown : public Button { + public: + explicit ButtonDropDown(Menu::Delegate* menu_delegate); + virtual ~ButtonDropDown(); + + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. + bool GetAccessibleDefaultAction(std::wstring* action); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + // Overriden from View. + virtual bool GetAccessibleState(VARIANT* state); + + private: + // Overridden from Button + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + virtual void OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled); + virtual bool OnMouseDragged(const ChromeViews::MouseEvent& e); + + // Internal function to show the dropdown menu + void ShowDropDownMenu(HWND window); + + // Specifies who to delegate populating the menu + Menu::Delegate* menu_delegate_; + + // Y position of mouse when left mouse button is pressed + int y_position_on_lbuttondown_; + + // A factory for tasks that show the dropdown context menu for the button. + ScopedRunnableMethodFactory<ButtonDropDown> show_menu_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(ButtonDropDown); +}; + +} // namespace + +#endif // CHROME_VIEWS_BUTTON_DROPDOWN_H__ diff --git a/chrome/views/checkbox.cc b/chrome/views/checkbox.cc new file mode 100644 index 0000000..56ac361 --- /dev/null +++ b/chrome/views/checkbox.cc @@ -0,0 +1,204 @@ +// 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 "chrome/views/checkbox.h" + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/label.h" + +// FIXME(ACW) there got be a better way to find out the check box sizes +static int kCheckBoxWidth = 13; +static int kCheckBoxHeight = 13; +static int kCheckBoxToLabel = 4; + +namespace ChromeViews { + +// Horizontal focus padding. +const int CheckBox::kFocusPaddingHorizontal = 2; +const int CheckBox::kFocusPaddingVertical = 1; + +const char CheckBox::kViewClassName[] = "chrome/views/CheckBox"; + +CheckBox::CheckBox(const std::wstring& label) + : NativeButton(label), + is_selected_(false) { + // Note: we paint the label as a floating view + SetMinSizeFromDLUs(gfx::Size(0, 0)); + label_ = new ChromeViews::Label(label); + label_->SetHorizontalAlignment(Label::ALIGN_LEFT); +} + +CheckBox::~CheckBox() { + delete label_; +} + +void CheckBox::SetMultiLine(bool multi_line) { + label_->SetMultiLine(multi_line); +} + +// static +int CheckBox::GetTextIndent() { + return kCheckBoxWidth + kCheckBoxToLabel + kFocusPaddingHorizontal; +} + +void CheckBox::SetIsSelected(bool f) { + if (f != is_selected_) { + is_selected_ = f; + UpdateNativeButton(); + } +} + +bool CheckBox::IsSelected() const { + return is_selected_; +} + +std::string CheckBox::GetClassName() const { + return kViewClassName; +} + +void CheckBox::Layout() { + int label_x = GetTextIndent(); + label_->SetBounds(label_x, 0, GetWidth() - label_x, GetHeight()); + if (hwnd_view_) { + int first_line_height = label_->GetFont().height(); + hwnd_view_->SetBounds(0, ((first_line_height - kCheckBoxHeight) / 2) + 1, + kCheckBoxWidth, kCheckBoxHeight); + hwnd_view_->UpdateHWNDBounds(); + } +} + +void CheckBox::ComputeTextRect(gfx::Rect* out) { + CSize s; + label_->GetPreferredSize(&s); + out->set_x(GetTextIndent()); + out->set_y(kFocusPaddingVertical); + int new_width = std::min(GetWidth() - (kCheckBoxWidth + kCheckBoxToLabel), + static_cast<int>(s.cx)); + out->set_width(std::max(0, new_width)); + out->set_height(s.cy); +} + +void CheckBox::Paint(ChromeCanvas* canvas) { + gfx::Rect r; + ComputeTextRect(&r); + // Paint the focus border if any. + if (HasFocus()) + canvas->DrawFocusRect(r.x() - kFocusPaddingHorizontal, + r.y() - kFocusPaddingVertical, + r.width() + kFocusPaddingHorizontal * 2, + r.height() + kFocusPaddingVertical * 2); + PaintFloatingView(canvas, label_, r.x(), r.y(), r.width(), r.height()); +} + +void CheckBox::SetEnabled(bool enabled) { + if (enabled_ == enabled) + return; + NativeButton::SetEnabled(enabled); + label_->SetEnabled(enabled); +} + +HWND CheckBox::CreateNativeControl(HWND parent_container) { + HWND r = ::CreateWindowEx(WS_EX_TRANSPARENT | GetAdditionalExStyle(), + L"BUTTON", + L"", + WS_CHILD | BS_CHECKBOX | WS_VISIBLE, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + ConfigureNativeButton(r); + return r; +} + +void CheckBox::ConfigureNativeButton(HWND hwnd) { + ::SendMessage(hwnd, + static_cast<UINT>(BM_SETCHECK), + static_cast<WPARAM>(is_selected_ ? BST_CHECKED : BST_UNCHECKED), + 0); + label_->SetText(GetLabel()); +} + +void CheckBox::GetPreferredSize(CSize *out) { + label_->GetPreferredSize(out); + out->cy = std::max(static_cast<int>(out->cy + kFocusPaddingVertical * 2), + kCheckBoxHeight); + out->cx += GetTextIndent() * 2; +} + +LRESULT CheckBox::OnCommand(UINT code, int id, HWND source) { + if (code == BN_CLICKED) + SetIsSelected(!is_selected_); + + return NativeButton::OnCommand(code, id, source); +} + +void CheckBox::HighlightButton(bool f) { + ::SendMessage(GetNativeControlHWND(), + static_cast<UINT>(BM_SETSTATE), + static_cast<WPARAM>(f), + 0); +} + +bool CheckBox::LabelHitTest(const MouseEvent& event) { + CPoint p(event.GetLocation()); + gfx::Rect r; + ComputeTextRect(&r); + return r.Contains(event.GetLocation().x, event.GetLocation().y); +} + +void CheckBox::OnMouseEntered(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); +} + +void CheckBox::OnMouseMoved(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); +} + +void CheckBox::OnMouseExited(const MouseEvent& event) { + HighlightButton(false); +} + +bool CheckBox::OnMousePressed(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); + return true; +} + +bool CheckBox::OnMouseDragged(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); + return true; +} + +void CheckBox::OnMouseReleased(const MouseEvent& event, + bool canceled) { + HighlightButton(false); + if (!canceled && LabelHitTest(event)) + OnCommand(BN_CLICKED, 0, GetNativeControlHWND()); +} + +} diff --git a/chrome/views/checkbox.h b/chrome/views/checkbox.h new file mode 100644 index 0000000..739bb89 --- /dev/null +++ b/chrome/views/checkbox.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef CHROME_VIEWS_CHECKBOX_H__ +#define CHROME_VIEWS_CHECKBOX_H__ + +#include "base/gfx/rect.h" +#include "chrome/views/native_button.h" + +namespace ChromeViews { + +class Label; + +//////////////////////////////////////////////////////////////////////////////// +// +// CheckBox implements a check box button. It uses the standard windows control +// for the check item but not for the label. We have to do this because windows +// always wants to draw a background under the label. I tried to intercept +// WM_CTLCOLORSTATIC and return a NULL_BRUSH and setting the BkMode to +// transparent as well as other things. The background was always drawn as solid +// black. +// +// The label is implemented with a ChromeViews::Label +// +//////////////////////////////////////////////////////////////////////////////// +class CheckBox : public NativeButton { + public: + static const char kViewClassName[]; + static const int kFocusPaddingHorizontal; + static const int kFocusPaddingVertical; + + explicit CheckBox(const std::wstring& label); + virtual ~CheckBox(); + + // Allows the label to wrap across multiple lines if |multi_line| is true. + // If false, the text is cropped. + void SetMultiLine(bool multi_line); + + // Returns the x position of the text. This can also be used to indent + // subsequent dependent controls. + static int GetTextIndent(); + + virtual void SetIsSelected(bool f); + bool IsSelected() const; + + virtual std::string GetClassName() const; + + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual void OnMouseEntered(const MouseEvent& event); + virtual void OnMouseMoved(const MouseEvent& event); + virtual void OnMouseExited(const MouseEvent& event); + + virtual void Paint(ChromeCanvas* canvas); + + // Overriden to forward to the label too. + virtual void SetEnabled(bool enabled); + + protected: + + virtual HWND CreateNativeControl(HWND parent_container); + virtual void ConfigureNativeButton(HWND hwnd); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + Label* label_; + private: + + void HighlightButton(bool f); + bool LabelHitTest(const MouseEvent& event); + void ComputeTextRect(gfx::Rect* out); + + bool is_selected_; + + + DISALLOW_EVIL_CONSTRUCTORS(CheckBox); +}; +} + +#endif // CHROME_VIEWS_NATIVE_CHECKBOX_H__ diff --git a/chrome/views/chrome_menu.cc b/chrome/views/chrome_menu.cc new file mode 100644 index 0000000..9bb8c76 --- /dev/null +++ b/chrome/views/chrome_menu.cc @@ -0,0 +1,2743 @@ +// 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 "chrome/views/chrome_menu.h" + +#include <windows.h> +#include <uxtheme.h> +#include <Vssym32.h> + +#include "base/base_drag_source.h" +#include "base/gfx/native_theme.h" +#include "base/gfx/skia_utils.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/timer.h" +#include "base/win_util.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/color_utils.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/views/border.h" +#include "chrome/views/hwnd_view_container.h" +#include "generated_resources.h" + +// Margins between the top of the item and the label. +static const int kItemTopMargin = 3; + +// Margins between the bottom of the item and the label. +static const int kItemBottomMargin = 4; + +// Margins between the left of the item and the icon. +static const int kItemLeftMargin = 4; + +// Padding between the label and submenu arrow. +static const int kLabelToArrowPadding = 10; + +// Padding between the arrow and the edge. +static const int kArrowToEdgePadding = 5; + +// Padding between the icon and label. +static const int kIconToLabelPadding = 8; + +// Padding between the gutter and label. +static const int kGutterToLabel = 5; + +// Height of the scroll arrow. +// This goes up to 4 with large fonts, but this is close enough for now. +static const int kScrollArrowHeight = 3; + +// Size of the check. This comes from the OS. +static int check_width; +static int check_height; + +// Size of the submenu arrow. This comes from the OS. +static int arrow_width; +static int arrow_height; + +// Width of the gutter. Only used if render_gutter is true. +static int gutter_width; + +// Margins between the right of the item and the label. +static int item_right_margin; + +// X-coordinate of where the label starts. +static int label_start; + +// Height of the separator. +static int separator_height; + +// Padding around the edges of the submenu. +static const int kSubmenuBorderSize = 3; + +// Amount to inset submenus. +static const int kSubmenuHorizontalInset = 3; + +// Delay, in ms, between when menus are selected are moused over and the menu +// appears. +static const int kShowDelay = 400; + +// Amount of time from when the drop exits the menu and the menu is hidden. +static const int kCloseOnExitTime = 1200; + +// Height of the drop indicator. This should be an event number. +static const int kDropIndicatorHeight = 2; + +// Color of the drop indicator. +static const SkColor kDropIndicatorColor = SK_ColorBLACK; + +// Whether or not the gutter should be rendered. The gutter is specific to +// Vista. +static bool render_gutter = false; + +// Max width of a menu. There does not appear to be an OS value for this, yet +// both IE and FF restrict the max width of a menu. +static const LONG kMaxMenuWidth = 400; + +// Period of the scroll timer (in milliseconds). +static const int kScrollTimerMS = 30; + +// Preferred height of menu items. Reset every time a menu is run. +static int pref_menu_height; + +using gfx::NativeTheme; + +namespace ChromeViews { + +// Calculates all sizes that we can from the OS. +// +// This is invoked prior to Running a menu. +void UpdateMenuPartSizes() { + HDC dc = GetDC(NULL); + RECT bounds = { 0, 0, 200, 200 }; + SIZE check_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, &bounds, + TS_TRUE, &check_size) == S_OK) { + check_width = check_size.cx; + check_height = check_size.cy; + } else { + check_width = GetSystemMetrics(SM_CXMENUCHECK); + check_height = GetSystemMetrics(SM_CYMENUCHECK); + } + + SIZE arrow_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPSUBMENU, MSM_NORMAL, &bounds, + TS_TRUE, &arrow_size) == S_OK) { + arrow_width = arrow_size.cx; + arrow_height = arrow_size.cy; + } else { + // Sadly I didn't see a specify metrics for this. + arrow_width = GetSystemMetrics(SM_CXMENUCHECK); + arrow_height = GetSystemMetrics(SM_CYMENUCHECK); + } + + SIZE gutter_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPGUTTER, MSM_NORMAL, &bounds, + TS_TRUE, &gutter_size) == S_OK) { + gutter_width = gutter_size.cx; + render_gutter = true; + } else { + gutter_width = 0; + render_gutter = false; + } + + SIZE separator_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPSEPARATOR, MSM_NORMAL, &bounds, + TS_TRUE, &separator_size) == S_OK) { + separator_height = separator_size.cy; + } else { + separator_height = GetSystemMetrics(SM_CYMENU) / 2; + } + + item_right_margin = kLabelToArrowPadding + arrow_width + kArrowToEdgePadding; + + label_start = kItemLeftMargin + check_width + kIconToLabelPadding; + if (render_gutter) + label_start += gutter_width + kGutterToLabel; + + ReleaseDC(NULL, dc); + + CSize pref; + MenuItemView menu_item(NULL); + menu_item.SetTitle(L"blah"); // Text doesn't matter here. + menu_item.GetPreferredSize(&pref); + pref_menu_height = pref.cy; +} + +namespace { + +// Convenience for scrolling the view such that the origin is visible. +static void ScrollToVisible(View* view) { + view->ScrollRectToVisible(0, 0, view->GetWidth(), view->GetHeight()); +} + +// MenuScrollTask -------------------------------------------------------------- + +// MenuScrollTask is used when the SubmenuView does not all fit on screen and +// the mouse is over the scroll up/down buttons. MenuScrollTask schedules itself +// with the TimerManager. When Run is invoked MenuScrollTask scrolls +// appropriately. + +class MenuScrollTask : public Task { +public: + MenuScrollTask() : submenu_(NULL) { + pixels_per_second_ = pref_menu_height * 20; + } + + virtual ~MenuScrollTask() { + StopScrolling(); + } + + virtual void Run() { + DCHECK(submenu_); + gfx::Rect vis_rect = submenu_->GetVisibleBounds(); + const int delta_y = static_cast<int>( + (Time::Now() - start_scroll_time_).InMilliseconds() * + pixels_per_second_ / 1000); + int target_y = start_y_; + if (is_scrolling_up_) + target_y = std::max(0, target_y - delta_y); + else + target_y = std::min(submenu_->GetHeight() - vis_rect.height(), + target_y + delta_y); + submenu_->ScrollRectToVisible(vis_rect.x(), target_y, vis_rect.width(), + vis_rect.height()); + } + + void Update(const MenuController::MenuPart& part) { + if (!part.is_scroll()) { + StopScrolling(); + return; + } + DCHECK(part.submenu); + SubmenuView* new_menu = part.submenu; + bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP); + if (new_menu == submenu_ && is_scrolling_up_ == new_is_up) + return; + + start_scroll_time_ = Time::Now(); + start_y_ = part.submenu->GetVisibleBounds().y(); + submenu_ = new_menu; + is_scrolling_up_ = new_is_up; + + if (!scrolling_timer_.get()) { + scrolling_timer_.reset(new Timer(kScrollTimerMS, this, true)); + TimerManager* tm = MessageLoop::current()->timer_manager(); + tm->StartTimer(scrolling_timer_.get()); + } + } + + void StopScrolling() { + if (scrolling_timer_.get()) { + TimerManager* tm = MessageLoop::current()->timer_manager(); + tm->StopTimer(scrolling_timer_.get()); + scrolling_timer_.reset(NULL); + submenu_ = NULL; + } + } + + // The menu being scrolled. Returns null if not scrolling. + SubmenuView* submenu() const { return submenu_; } + + private: + // SubmenuView being scrolled. + SubmenuView* submenu_; + + // Direction scrolling. + bool is_scrolling_up_; + + // Timer to periodically scroll. + scoped_ptr<Timer> scrolling_timer_; + + // Time we started scrolling at. + Time start_scroll_time_; + + // How many pixels to scroll per second. + int pixels_per_second_; + + // Y-coordinate of submenu_view_ when scrolling started. + int start_y_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuScrollTask); +}; + +// MenuScrollButton ------------------------------------------------------------ + +// MenuScrollButton is used for the scroll buttons when not all menu items fit +// on screen. MenuScrollButton forwards appropriate events to the +// MenuController. + +class MenuScrollButton : public View { + public: + explicit MenuScrollButton(SubmenuView* host, bool is_up) + : host_(host), + is_up_(is_up), + // Make our height the same as that of other MenuItemViews. + pref_height_(pref_menu_height) { + } + + virtual void GetPreferredSize(CSize* out) { + out->cx = kScrollArrowHeight * 2 - 1; + out->cy = pref_height_; + } + + virtual bool CanDrop(const OSExchangeData& data) { + DCHECK(host_->GetMenuItem()->GetMenuController()); + return true; // Always return true so that drop events are targeted to us. + } + + virtual void OnDragEntered(const DropTargetEvent& event) { + DCHECK(host_->GetMenuItem()->GetMenuController()); + host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton( + host_, is_up_); + } + + virtual int OnDragUpdated(const DropTargetEvent& event) { + return DragDropTypes::DRAG_NONE; + } + + virtual void OnDragExited() { + DCHECK(host_->GetMenuItem()->GetMenuController()); + host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_); + } + + virtual int OnPerformDrop(const DropTargetEvent& event) { + return DragDropTypes::DRAG_NONE; + } + + virtual void Paint(ChromeCanvas* canvas) { + HDC dc = canvas->beginPlatformPaint(); + + // The background. + RECT item_bounds = { 0, 0, GetWidth(), GetHeight() }; + NativeTheme::instance()->PaintMenuItemBackground( + NativeTheme::MENU, dc, MENU_POPUPITEM, MPI_NORMAL, false, + &item_bounds); + + // Then the arrow. + int x = GetWidth() / 2; + int y = (GetHeight() - kScrollArrowHeight) / 2; + int delta_y = 1; + if (!is_up_) { + delta_y = -1; + y += kScrollArrowHeight; + } + SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT); + for (int i = 0; i < kScrollArrowHeight; ++i, --x, y += delta_y) + canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1); + + canvas->endPlatformPaint(); + } + + private: + // SubmenuView we were created for. + SubmenuView* host_; + + // Direction of the button. + bool is_up_; + + // Preferred height. + int pref_height_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuScrollButton); +}; + +// MenuScrollView -------------------------------------------------------------- + +// MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so +// that ScrollRectToVisible works. +// +// NOTE: It is possible to use ScrollView directly (after making it deal with +// null scrollbars), but clicking on a child of ScrollView forces the window to +// become active, which we don't want. As we really only need a fraction of +// what ScrollView does, we use a one off variant. + +class MenuScrollView : public View { + public: + explicit MenuScrollView(View* child) { + AddChildView(child); + } + + virtual void ScrollRectToVisible(int x, int y, int width, int height) { + // NOTE: this assumes we only want to scroll in the y direction. + + View* child = GetContents(); + // Convert y to view's coordinates. + y -= child->GetY(); + CSize pref; + child->GetPreferredSize(&pref); + // Constrain y to make sure we don't show past the bottom of the view. + y = std::max(0, std::min(static_cast<int>(pref.cy) - GetHeight(), y)); + child->SetY(-y); + } + + // Returns the contents, which is the SubmenuView. + View* GetContents() { + return GetChildViewAt(0); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(MenuScrollView); +}; + +// MenuScrollViewContainer ----------------------------------------------------- + +// MenuScrollViewContainer contains the SubmenuView (through a MenuScrollView) +// and two scroll buttons. The scroll buttons are only visible and enabled if +// the preferred height of the SubmenuView is bigger than our bounds. +class MenuScrollViewContainer : public View { + public: + explicit MenuScrollViewContainer(SubmenuView* content_view) { + scroll_up_button_ = new MenuScrollButton(content_view, true); + scroll_down_button_ = new MenuScrollButton(content_view, false); + AddChildView(scroll_up_button_); + AddChildView(scroll_down_button_); + + scroll_view_ = new MenuScrollView(content_view); + AddChildView(scroll_view_); + + SetBorder( + Border::CreateEmptyBorder(kSubmenuBorderSize, kSubmenuBorderSize, + kSubmenuBorderSize, kSubmenuBorderSize)); + } + + virtual void Paint(ChromeCanvas* canvas) { + HDC dc = canvas->beginPlatformPaint(); + CRect bounds(0, 0, GetWidth(), GetHeight()); + NativeTheme::instance()->PaintMenuBackground( + NativeTheme::MENU, dc, MENU_POPUPBACKGROUND, 0, &bounds); + canvas->endPlatformPaint(); + } + + View* scroll_down_button() { return scroll_down_button_; } + + View* scroll_up_button() { return scroll_up_button_; } + + virtual void Layout() { + gfx::Insets insets = GetInsets(); + int x = insets.left(); + int y = insets.top(); + int width = GetWidth() - insets.width(); + int content_height = GetHeight() - insets.height(); + if (!scroll_up_button_->IsVisible()) { + scroll_view_->SetBounds(x, y, width, content_height); + scroll_view_->Layout(); + return; + } + + CSize pref; + scroll_up_button_->GetPreferredSize(&pref); + scroll_up_button_->SetBounds(x, y, width, pref.cy); + content_height -= pref.cy; + + const int scroll_view_y = y + pref.cy; + + scroll_down_button_->GetPreferredSize(&pref); + scroll_down_button_->SetBounds(x, GetHeight() - pref.cy - insets.top(), + width, pref.cy); + content_height -= pref.cy; + + scroll_view_->SetBounds(x, scroll_view_y, width, content_height); + scroll_view_->Layout(); + } + + virtual void DidChangeBounds(const CRect& previous, const CRect& current) { + CSize content_pref; + scroll_view_->GetContents()->GetPreferredSize(&content_pref); + scroll_up_button_->SetVisible(content_pref.cy > GetHeight()); + scroll_down_button_->SetVisible(content_pref.cy > GetHeight()); + } + + virtual void GetPreferredSize(CSize* out) { + scroll_view_->GetContents()->GetPreferredSize(out); + gfx::Insets insets = GetInsets(); + out->cx += insets.width(); + out->cy += insets.height(); + } + + private: + // The scroll buttons. + View* scroll_up_button_; + View* scroll_down_button_; + + // The scroll view. + MenuScrollView* scroll_view_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuScrollViewContainer); +}; + +// MenuSeparator --------------------------------------------------------------- + +// Renders a separator. + +class MenuSeparator : public View { + public: + MenuSeparator() { + } + + void Paint(ChromeCanvas* canvas) { + // The gutter is rendered before the background. + int start_x = 0; + int start_y = GetHeight() / 3; + HDC dc = canvas->beginPlatformPaint(); + if (render_gutter) { + // If render_gutter is true, we're on Vista and need to render the + // gutter, then indent the separator from the gutter. + RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0, + GetHeight() }; + gutter_bounds.right = gutter_bounds.left + gutter_width; + NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, + &gutter_bounds); + start_x = gutter_bounds.left + gutter_width; + start_y = 0; + } + RECT separator_bounds = { start_x, start_y, GetWidth(), GetHeight() }; + NativeTheme::instance()->PaintMenuSeparator(dc, MENU_POPUPSEPARATOR, + MPI_NORMAL, &separator_bounds); + canvas->endPlatformPaint(); + } + + void GetPreferredSize(CSize* out) { + out->cx = 10; // Just in case we're the only item in a menu. + out->cy = separator_height; + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(MenuSeparator); +}; + +// MenuHostRootView ---------------------------------------------------------- + +// MenuHostRootView is the RootView of the window showing the menu. +// SubmenuView's scroll view is added as a child of MenuHostRootView. +// MenuHostRootView forwards relevant events to the MenuController. +// +// As all the menu items are owned by the root menu item, care must be taken +// such that when MenuHostRootView is deleted it doesn't delete the menu items. + +class MenuHostRootView : public RootView { + public: + explicit MenuHostRootView(ViewContainer* container, + SubmenuView* submenu) + : RootView(container, true), + submenu_(submenu), + forward_drag_to_menu_controller_(true), + suspend_events_(false) { +#ifdef DEBUG_MENU + DLOG(INFO) << " new MenuHostRootView " << this; +#endif + } + + virtual bool OnMousePressed(const MouseEvent& event) { + if (suspend_events_) + return true; + + forward_drag_to_menu_controller_ = + ((event.GetX() < 0 || event.GetY() < 0 || event.GetX() >= GetWidth() || + event.GetY() >= GetHeight()) || + !RootView::OnMousePressed(event)); + if (forward_drag_to_menu_controller_) + GetMenuController()->OnMousePressed(submenu_, event); + return true; + } + + virtual bool OnMouseDragged(const MouseEvent& event) { + if (suspend_events_) + return true; + + if (forward_drag_to_menu_controller_) { +#ifdef DEBUG_MENU + DLOG(INFO) << " MenuHostRootView::OnMouseDragged source=" << submenu_; +#endif + GetMenuController()->OnMouseDragged(submenu_, event); + return true; + } + return RootView::OnMouseDragged(event); + } + + virtual void OnMouseReleased(const MouseEvent& event, bool canceled) { + if (suspend_events_) + return; + + RootView::OnMouseReleased(event, canceled); + if (forward_drag_to_menu_controller_) { + if (canceled) { + GetMenuController()->Cancel(true); + } else { + GetMenuController()->OnMouseReleased(submenu_, event); + } + forward_drag_to_menu_controller_ = false; + } + } + + virtual void OnMouseMoved(const MouseEvent& event) { + if (suspend_events_) + return; + + RootView::OnMouseMoved(event); + GetMenuController()->OnMouseMoved(submenu_, event); + } + + virtual void ProcessOnMouseExited() { + if (suspend_events_) + return; + + RootView::ProcessOnMouseExited(); + } + + virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e) { + // RootView::ProcessMouseWheelEvent forwards to the focused view. We don't + // have a focused view, so we need to override this then forward to + // the menu. + return submenu_->OnMouseWheel(e); + } + + void SuspendEvents() { + suspend_events_ = true; + } + + private: + MenuController* GetMenuController() { + return submenu_->GetMenuItem()->GetMenuController(); + } + + /// The SubmenuView we contain. + SubmenuView* submenu_; + + // Whether mouse dragged/released should be forwarded to the MenuController. + bool forward_drag_to_menu_controller_; + + // Whether events are suspended. If true, no events are forwarded to the + // MenuController. + bool suspend_events_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuHostRootView); +}; + +// MenuHost ------------------------------------------------------------------ + +// MenuHost is the window responsible for showing a single menu. +// +// Similar to MenuHostRootView, care must be taken such that when MenuHost is +// deleted, it doesn't delete the menu items. MenuHost is closed via a +// DelayedClosed, which avoids timing issues with deleting the window while +// capture or events are directed at it. + +class MenuHost : public HWNDViewContainer { + public: + MenuHost(SubmenuView* submenu) + : closed_(false), + submenu_(submenu), + owns_capture_(false) { + set_window_style(WS_POPUP); + set_initial_class_style( + (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? + 0 : CS_DROPSHADOW); + is_mouse_down_ = + ((GetKeyState(VK_LBUTTON) & 0x80) || + (GetKeyState(VK_RBUTTON) & 0x80) || + (GetKeyState(VK_MBUTTON) & 0x80) || + (GetKeyState(VK_XBUTTON1) & 0x80) || + (GetKeyState(VK_XBUTTON2) & 0x80)); + // Mouse clicks shouldn't give us focus. + set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE); + } + + void Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + bool do_capture) { + HWNDViewContainer::Init(parent, bounds, contents_view, true); + // We don't want to take focus away from the hosting window. + ShowWindow(SW_SHOWNA); + owns_capture_ = do_capture; + if (do_capture) { + SetCapture(); + has_capture_ = true; +#ifdef DEBUG_MENU + DLOG(INFO) << "Doing capture"; +#endif + } + } + + virtual void Hide() { + if (closed_) { + // We're already closed, nothing to do. + // This is invoked twice if the first time just hid us, and the second + // time deleted Closed (deleted) us. + return; + } + // The menus are freed separately, and possibly before the window is closed, + // remove them so that View doesn't try to access deleted objects. + static_cast<MenuHostRootView*>(GetRootView())->SuspendEvents(); + GetRootView()->RemoveAllChildViews(false); + closed_ = true; + ReleaseCapture(); + HWNDViewContainer::Hide(); + } + + virtual void OnCaptureChanged(HWND hwnd) { + HWNDViewContainer::OnCaptureChanged(hwnd); + owns_capture_ = false; +#ifdef DEBUG_MENU + DLOG(INFO) << "Capture changed"; +#endif + } + + void ReleaseCapture() { + if (owns_capture_) { +#ifdef DEBUG_MENU + DLOG(INFO) << "released capture"; +#endif + owns_capture_ = false; + ::ReleaseCapture(); + } + } + + protected: + // Overriden to create MenuHostRootView. + virtual RootView* CreateRootView() { + return new MenuHostRootView(this, submenu_); + } + + virtual void OnCancelMode() { + if (!closed_) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnCanceMode, closing menu"; +#endif + submenu_->GetMenuItem()->GetMenuController()->Cancel(true); + } + } + + // Overriden to return false, we do NOT want to release capture on mouse + // release. + virtual bool ReleaseCaptureOnMouseReleased() { + return false; + } + + private: + // If true, we've been closed. + bool closed_; + + // If true, we own the capture and need to release it. + bool owns_capture_; + + // The view we contain. + SubmenuView* submenu_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuHost); +}; + +// EmptyMenuMenuItem --------------------------------------------------------- + +// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem +// is itself a MenuItemView, but it uses a different ID so that it isn't +// identified as a MenuItemView. + +class EmptyMenuMenuItem : public MenuItemView { + public: + // ID used for EmptyMenuMenuItem. + static const int kEmptyMenuItemViewID; + + EmptyMenuMenuItem(MenuItemView* parent) : MenuItemView(parent, 0, NORMAL) { + SetTitle(l10n_util::GetString(IDS_MENU_EMPTY_SUBMENU)); + // Set this so that we're not identified as a normal menu item. + SetID(kEmptyMenuItemViewID); + SetEnabled(false); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(EmptyMenuMenuItem); +}; + +// static +const int EmptyMenuMenuItem::kEmptyMenuItemViewID = + MenuItemView::kMenuItemViewID + 1; + +} // namespace + +// SubmenuView --------------------------------------------------------------- + +SubmenuView::SubmenuView(MenuItemView* parent) + : parent_menu_item_(parent), + host_(NULL), + drop_item_(NULL), + scroll_view_container_(NULL) { + DCHECK(parent); + // We'll delete ourselves, otherwise the ScrollView would delete us on close. + SetParentOwned(false); +} + +SubmenuView::~SubmenuView() { + DCHECK(!host_); + delete scroll_view_container_; +} + +int SubmenuView::GetMenuItemCount() { + int count = 0; + for (int i = 0; i < GetChildViewCount(); ++i) { + if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID) + count++; + } + return count; +} + +MenuItemView* SubmenuView::GetMenuItemAt(int index) { + for (int i = 0, count = 0; i < GetChildViewCount(); ++i) { + if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID && + count++ == index) { + return static_cast<MenuItemView*>(GetChildViewAt(i)); + } + } + NOTREACHED(); + return NULL; +} + +void SubmenuView::Layout() { + // We're in a ScrollView, and need to set our width/height ourselves. + View* parent = GetParent(); + if (!parent) + return; + CSize pref; + GetPreferredSize(&pref); + SetBounds(GetX(), GetY(), parent->GetWidth(), pref.cy); + + gfx::Insets insets = GetInsets(); + int x = insets.left(); + int y = insets.top(); + int menu_item_width = GetWidth() - insets.width(); + for (int i = 0; i < GetChildViewCount(); ++i) { + View* child = GetChildViewAt(i); + CSize child_pref_size; + child->GetPreferredSize(&child_pref_size); + child->SetBounds(x, y, menu_item_width, child_pref_size.cy); + y += child_pref_size.cy; + } +} + +void SubmenuView::GetPreferredSize(CSize* out) { + if (GetChildViewCount() == 0) { + out->SetSize(0, 0); + return; + } + + int max_width = 0; + int height = 0; + for (int i = 0; i < GetChildViewCount(); ++i) { + View* child = GetChildViewAt(i); + CSize child_pref_size; + child->GetPreferredSize(&child_pref_size); + max_width = std::max(max_width, static_cast<int>(child_pref_size.cx)); + height += child_pref_size.cy; + } + gfx::Insets insets = GetInsets(); + out->SetSize(max_width + insets.width(), height + insets.height()); +} + +void SubmenuView::DidChangeBounds(const CRect& previous, const CRect& current) { + SchedulePaint(); +} + +void SubmenuView::PaintChildren(ChromeCanvas* canvas) { + View::PaintChildren(canvas); + + if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON) + PaintDropIndicator(canvas, drop_item_, drop_position_); +} + +bool SubmenuView::CanDrop(const OSExchangeData& data) { + DCHECK(GetMenuItem()->GetMenuController()); + return GetMenuItem()->GetMenuController()->CanDrop(this, data); +} + +void SubmenuView::OnDragEntered(const DropTargetEvent& event) { + DCHECK(GetMenuItem()->GetMenuController()); + GetMenuItem()->GetMenuController()->OnDragEntered(this, event); +} + +int SubmenuView::OnDragUpdated(const DropTargetEvent& event) { + DCHECK(GetMenuItem()->GetMenuController()); + return GetMenuItem()->GetMenuController()->OnDragUpdated(this, event); +} + +void SubmenuView::OnDragExited() { + DCHECK(GetMenuItem()->GetMenuController()); + GetMenuItem()->GetMenuController()->OnDragExited(this); +} + +int SubmenuView::OnPerformDrop(const DropTargetEvent& event) { + DCHECK(GetMenuItem()->GetMenuController()); + return GetMenuItem()->GetMenuController()->OnPerformDrop(this, event); +} + +bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) { + gfx::Rect vis_bounds = GetVisibleBounds(); + int menu_item_count = GetMenuItemCount(); + if (vis_bounds.height() == GetHeight() || !menu_item_count) { + // All menu items are visible, nothing to scroll. + return true; + } + + // Find the index of the first menu item whose y-coordinate is >= visible + // y-coordinate. + int first_vis_index = -1; + for (int i = 0; i < menu_item_count; ++i) { + MenuItemView* menu_item = GetMenuItemAt(i); + if (menu_item->GetY() == vis_bounds.y()) { + first_vis_index = i; + break; + } else if (menu_item->GetY() > vis_bounds.y()) { + first_vis_index = std::max(0, i - 1); + break; + } + } + if (first_vis_index == -1) + return true; + + // If the first item isn't entirely visible, make it visible, otherwise make + // the next/previous one entirely visible. + int delta = abs(e.GetOffset() / WHEEL_DELTA); + bool scroll_up = (e.GetOffset() > 0); + while (delta-- > 0) { + int scroll_amount = 0; + if (scroll_up) { + if (GetMenuItemAt(first_vis_index)->GetY() == vis_bounds.y()) { + if (first_vis_index != 0) { + scroll_amount = GetMenuItemAt(first_vis_index - 1)->GetY() - + vis_bounds.y(); + first_vis_index--; + } else { + break; + } + } else { + scroll_amount = GetMenuItemAt(first_vis_index)->GetY() - vis_bounds.y(); + } + } else { + if (first_vis_index + 1 == GetMenuItemCount()) + break; + scroll_amount = GetMenuItemAt(first_vis_index + 1)->GetY() - + vis_bounds.y(); + if (GetMenuItemAt(first_vis_index)->GetY() == vis_bounds.y()) + first_vis_index++; + } + ScrollRectToVisible(0, vis_bounds.y() + scroll_amount, vis_bounds.width(), + vis_bounds.height()); + vis_bounds = GetVisibleBounds(); + } + + return true; +} + +void SubmenuView::ShowAt(HWND parent, + const gfx::Rect& bounds, + bool do_capture) { + DCHECK(!host_); + host_ = new MenuHost(this); + // Force construction of the scroll view container. + GetScrollViewContainer(); + // Make sure the first row is visible. + ScrollRectToVisible(0, 0, 1, 1); + host_->Init(parent, bounds, scroll_view_container_, do_capture); +} + +void SubmenuView::Close(bool destroy_host) { + if (host_) { + if (destroy_host) { + host_->Close(); + host_ = NULL; + } else { + host_->Hide(); + } + } +} + +void SubmenuView::ReleaseCapture() { + host_->ReleaseCapture(); +} + +void SubmenuView::SetDropMenuItem(MenuItemView* item, + MenuDelegate::DropPosition position) { + if (drop_item_ == item && drop_position_ == position) + return; + SchedulePaintForDropIndicator(drop_item_, drop_position_); + drop_item_ = item; + drop_position_ = position; + SchedulePaintForDropIndicator(drop_item_, drop_position_); +} + +bool SubmenuView::GetShowSelection(MenuItemView* item) { + if (drop_item_ == NULL) + return true; + // Something is being dropped on one of this menus items. Show the + // selection if the drop is on the passed in item and the drop position is + // ON. + return (drop_item_ == item && drop_position_ == MenuDelegate::DROP_ON); +} + +MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() { + if (!scroll_view_container_) { + scroll_view_container_ = new MenuScrollViewContainer(this); + // Otherwise MenuHost would delete us. + scroll_view_container_->SetParentOwned(false); + } + return scroll_view_container_; +} + +void SubmenuView::PaintDropIndicator(ChromeCanvas* canvas, + MenuItemView* item, + MenuDelegate::DropPosition position) { + if (position == MenuDelegate::DROP_NONE) + return; + + gfx::Rect bounds = CalculateDropIndicatorBounds(item, position); + canvas->FillRectInt(kDropIndicatorColor, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +} + +void SubmenuView::SchedulePaintForDropIndicator( + MenuItemView* item, + MenuDelegate::DropPosition position) { + if (item == NULL) + return; + + if (position == MenuDelegate::DROP_ON) { + item->SchedulePaint(); + } else if (position != MenuDelegate::DROP_NONE) { + gfx::Rect bounds = CalculateDropIndicatorBounds(item, position); + SchedulePaint(bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } +} + +gfx::Rect SubmenuView::CalculateDropIndicatorBounds( + MenuItemView* item, + MenuDelegate::DropPosition position) { + DCHECK(position != MenuDelegate::DROP_NONE); + CRect item_bounds_c; + item->GetBounds(&item_bounds_c); + gfx::Rect item_bounds(item_bounds_c); + switch (position) { + case MenuDelegate::DROP_BEFORE: + item_bounds.Offset(0, -kDropIndicatorHeight / 2); + item_bounds.set_height(kDropIndicatorHeight); + return item_bounds; + + case MenuDelegate::DROP_AFTER: + item_bounds.Offset(0, item_bounds.height() - kDropIndicatorHeight / 2); + item_bounds.set_height(kDropIndicatorHeight); + return item_bounds; + + default: + // Don't render anything for on. + return gfx::Rect(); + } +} + +// MenuItemView --------------------------------------------------------------- + +// static +const int MenuItemView::kMenuItemViewID = 1001; + +// static +const int MenuItemView::kDropBetweenPixels = 5; + +MenuItemView::MenuItemView(MenuDelegate* delegate) { + DCHECK(delegate_); + Init(NULL, 0, SUBMENU, delegate); +} + +MenuItemView::~MenuItemView() { + if (controller_) { + // We're currently showing. + + // We can't delete ourselves while we're blocking. + DCHECK(!controller_->IsBlockingRun()); + + // Invoking Cancel is going to call us back and notify the delegate. + // Notifying the delegate from the destructor can be problematic. To avoid + // this the delegate is set to NULL. + delegate_ = NULL; + + controller_->Cancel(true); + } + delete submenu_; +} + +void MenuItemView::RunMenuAt(HWND parent, + const gfx::Rect& bounds, + AnchorPosition anchor, + bool show_mnemonics) { + PrepareForRun(show_mnemonics); + + int mouse_event_flags; + + MenuController* controller = MenuController::GetActiveInstance(); + bool owns_controller = false; + if (!controller) { + // No menus are showing, show one. + controller = new MenuController(true); + MenuController::SetActiveInstance(controller); + owns_controller = true; + } else { + // A menu is already showing, use the same controller. + + // Don't support blocking from within non-blocking. + DCHECK(controller->IsBlockingRun()); + } + + controller_ = controller; + + // Run the loop. + MenuItemView* result = + controller->Run(parent, this, bounds, anchor, &mouse_event_flags); + + RemoveEmptyMenus(); + + controller_ = NULL; + + if (owns_controller) { + // We created the controller and need to delete it. + if (MenuController::GetActiveInstance() == controller) + MenuController::SetActiveInstance(NULL); + delete controller; + } + // Make sure all the windows we created to show the menus have been + // destroyed. + DestroyAllMenuHosts(); + if (result && delegate_) + delegate_->ExecuteCommand(result->GetCommand(), mouse_event_flags); +} + +void MenuItemView::RunMenuForDropAt(HWND parent, + const gfx::Rect& bounds, + AnchorPosition anchor) { + PrepareForRun(false); + + // If there is a menu, hide it so that only one menu is shown during dnd. + MenuController* current_controller = MenuController::GetActiveInstance(); + if (current_controller) { + current_controller->Cancel(true); + } + + // Always create a new controller for non-blocking. + controller_ = new MenuController(false); + + // Set the instance, that way it can be canceled by another menu. + MenuController::SetActiveInstance(controller_); + + controller_->Run(parent, this, bounds, anchor, NULL); +} + +void MenuItemView::Cancel() { + if (controller_ && !canceled_) { + canceled_ = true; + controller_->Cancel(true); + } +} + +SubmenuView* MenuItemView::CreateSubmenu() { + if (!submenu_) + submenu_ = new SubmenuView(this); + return submenu_; +} + +void MenuItemView::SetSelected(bool selected) { + selected_ = selected; + SchedulePaint(); +} + +void MenuItemView::SetIcon(const SkBitmap& icon, int item_id) { + MenuItemView* item = GetDescendantByID(item_id); + DCHECK(item); + item->SetIcon(icon); +} + +void MenuItemView::SetIcon(const SkBitmap& icon) { + icon_ = icon; + SchedulePaint(); +} + +void MenuItemView::Paint(ChromeCanvas* canvas) { + Paint(canvas, false); +} + +void MenuItemView::GetPreferredSize(CSize* out) { + out->cx = font_.GetStringWidth(title_) + label_start + item_right_margin; + out->cy = font_.height() + kItemBottomMargin + kItemTopMargin; +} + +MenuController* MenuItemView::GetMenuController() { + return GetRootMenuItem()->controller_; +} + +MenuDelegate* MenuItemView::GetDelegate() { + return GetRootMenuItem()->delegate_; +} + +MenuItemView* MenuItemView::GetRootMenuItem() { + MenuItemView* item = this; + while (item) { + MenuItemView* parent = item->GetParentMenuItem(); + if (!parent) + return item; + item = parent; + } + NOTREACHED(); + return NULL; +} + +wchar_t MenuItemView::GetMnemonic() { + const std::wstring& title = GetTitle(); + size_t index = 0; + do { + index = title.find('&', index); + if (index != std::wstring::npos) { + if (index + 1 != title.size() && title[index + 1] != '&') + return title[index + 1]; + index++; + } + } while (index != std::wstring::npos); + return 0; +} + +MenuItemView::MenuItemView(MenuItemView* parent, + int command, + MenuItemView::Type type) { + Init(parent, command, type, NULL); +} + +void MenuItemView::Init(MenuItemView* parent, + int command, + MenuItemView::Type type, + MenuDelegate* delegate) { + delegate_ = delegate; + controller_ = NULL; + canceled_ = false; + parent_menu_item_ = parent; + type_ = type; + selected_ = false; + command_ = command; + submenu_ = NULL; + // Assign our ID, this allows SubmenuItemView to find MenuItemViews. + SetID(kMenuItemViewID); + + MenuDelegate* root_delegate = GetDelegate(); + if (root_delegate) + SetEnabled(root_delegate->IsCommandEnabled(command)); +} + +MenuItemView* MenuItemView::AppendMenuItemInternal(int item_id, + const std::wstring& label, + const SkBitmap& icon, + Type type) { + if (!submenu_) + CreateSubmenu(); + if (type == SEPARATOR) { + submenu_->AddChildView(new MenuSeparator()); + return NULL; + } + MenuItemView* item = new MenuItemView(this, item_id, type); + if (label.empty() && GetDelegate()) + item->SetTitle(GetDelegate()->GetLabel(item_id)); + else + item->SetTitle(label); + item->SetIcon(icon); + if (type == SUBMENU) + item->CreateSubmenu(); + submenu_->AddChildView(item); + return item; +} + +MenuItemView* MenuItemView::GetDescendantByID(int id) { + if (GetCommand() == id) + return this; + if (!HasSubmenu()) + return NULL; + for (int i = 0; i < GetSubmenu()->GetChildViewCount(); ++i) { + View* child = GetSubmenu()->GetChildViewAt(i); + if (child->GetID() == MenuItemView::kMenuItemViewID) { + MenuItemView* result = static_cast<MenuItemView*>(child)-> + GetDescendantByID(id); + if (result) + return result; + } + } + return NULL; +} + +void MenuItemView::DropMenuClosed(bool notify_delegate) { + DCHECK(controller_); + DCHECK(!controller_->IsBlockingRun()); + if (MenuController::GetActiveInstance() == controller_) + MenuController::SetActiveInstance(NULL); + delete controller_; + controller_ = NULL; + + RemoveEmptyMenus(); + + if (notify_delegate && delegate_) { + // Our delegate is null when invoked from the destructor. + delegate_->DropMenuClosed(this); + } + // WARNING: its possible the delegate deleted us at this point. +} + +void MenuItemView::PrepareForRun(bool show_mnemonics) { + // Currently we only support showing the root. + DCHECK(!parent_menu_item_); + + // Don't invoke run from within run on the same menu. + DCHECK(!controller_); + + // Force us to have a submenu. + CreateSubmenu(); + + canceled_ = false; + + show_mnemonics_ = show_mnemonics; + + AddEmptyMenus(); + + UpdateMenuPartSizes(); +} + +int MenuItemView::GetDrawStringFlags() { + int flags = 0; + if (UILayoutIsRightToLeft()) + flags |= ChromeCanvas::TEXT_ALIGN_RIGHT; + else + flags |= ChromeCanvas::TEXT_ALIGN_LEFT; + + return flags | + (show_mnemonics_ ? ChromeCanvas::SHOW_PREFIX : ChromeCanvas::HIDE_PREFIX); +} + +void MenuItemView::AddEmptyMenus() { + DCHECK(HasSubmenu()); + if (submenu_->GetChildViewCount() == 0) { + submenu_->AddChildView(0, new EmptyMenuMenuItem(this)); + } else { + for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; + ++i) { + MenuItemView* child = submenu_->GetMenuItemAt(i); + if (child->HasSubmenu()) + child->AddEmptyMenus(); + } + } +} + +void MenuItemView::RemoveEmptyMenus() { + DCHECK(HasSubmenu()); + // Iterate backwards as we may end up removing views, which alters the child + // view count. + for (int i = submenu_->GetChildViewCount() - 1; i >= 0; --i) { + View* child = submenu_->GetChildViewAt(i); + if (child->GetID() == MenuItemView::kMenuItemViewID) { + MenuItemView* menu_item = static_cast<MenuItemView*>(child); + if (menu_item->HasSubmenu()) + menu_item->RemoveEmptyMenus(); + } else if (child->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) { + submenu_->RemoveChildView(child); + } + } +} + +void MenuItemView::AdjustBoundsForRTLUI(RECT* rect) const { + gfx::Rect mirrored_rect(*rect); + mirrored_rect.set_x(MirroredLeftPointForRect(mirrored_rect)); + *rect = mirrored_rect.ToRECT(); +} + +void MenuItemView::Paint(ChromeCanvas* canvas, bool for_drag) { + bool render_selection = + (!for_drag && IsSelected() && + parent_menu_item_->GetSubmenu()->GetShowSelection(this)); + int state = render_selection ? MPI_HOT : + (IsEnabled() ? MPI_NORMAL : MPI_DISABLED); + HDC dc = canvas->beginPlatformPaint(); + + // The gutter is rendered before the background. + if (render_gutter && !for_drag) { + RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0, + GetHeight() }; + gutter_bounds.right = gutter_bounds.left + gutter_width; + AdjustBoundsForRTLUI(&gutter_bounds); + NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, &gutter_bounds); + } + + // Render the background. + if (!for_drag) { + RECT item_bounds = { 0, 0, GetWidth(), GetHeight() }; + AdjustBoundsForRTLUI(&item_bounds); + NativeTheme::instance()->PaintMenuItemBackground( + NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection, + &item_bounds); + } + + int icon_x = kItemLeftMargin; + int icon_y = kItemTopMargin + (GetHeight() - kItemTopMargin - + kItemBottomMargin - check_height) / 2; + int icon_height = check_height; + int icon_width = check_width; + + if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) { + // Draw the check background. + RECT check_bg_bounds = { 0, 0, icon_x + icon_width, GetHeight() }; + const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED; + AdjustBoundsForRTLUI(&check_bg_bounds); + NativeTheme::instance()->PaintMenuCheckBackground( + NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state, + &check_bg_bounds); + + // And the check. + RECT check_bounds = { icon_x, icon_y, icon_x + icon_width, + icon_y + icon_height }; + const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL : + MC_CHECKMARKDISABLED; + AdjustBoundsForRTLUI(&check_bounds); + NativeTheme::instance()->PaintMenuCheck( + NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_bounds, + render_selection); + } + + // Render the foreground. + // Menu color is specific to Vista, fallback to classic colors if can't + // get color. + int default_sys_color = render_selection ? COLOR_HIGHLIGHTTEXT : + (IsEnabled() ? COLOR_MENUTEXT : COLOR_GRAYTEXT); + SkColor fg_color = NativeTheme::instance()->GetThemeColorWithDefault( + NativeTheme::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR, default_sys_color); + int width = GetWidth() - item_right_margin - label_start; + gfx::Rect text_bounds(label_start, kItemTopMargin, width, font_.height()); + text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); + canvas->DrawStringInt(GetTitle(), font_, fg_color, text_bounds.x(), + text_bounds.y(), text_bounds.width(), + text_bounds.height(), + GetRootMenuItem()->GetDrawStringFlags()); + + if (icon_.width() > 0) { + gfx::Rect icon_bounds(kItemLeftMargin, + kItemTopMargin + (GetHeight() - kItemTopMargin - + kItemBottomMargin - icon_.height()) / 2, + icon_.width(), + icon_.height()); + icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); + canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); + } + + if (HasSubmenu()) { + int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED; + RECT arrow_bounds = { GetWidth() - item_right_margin + kLabelToArrowPadding, + 0, 0, GetHeight() }; + arrow_bounds.right = arrow_bounds.left + arrow_width; + AdjustBoundsForRTLUI(&arrow_bounds); + + // If our sub menus open from right to left (which is the case when the + // locale is RTL) then we should make sure the menu arrow points to the + // right direction. + NativeTheme::MenuArrowDirection arrow_direction; + if (UILayoutIsRightToLeft()) + arrow_direction = NativeTheme::LEFT_POINTING_ARROW; + else + arrow_direction = NativeTheme::RIGHT_POINTING_ARROW; + + NativeTheme::instance()->PaintMenuArrow( + NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_bounds, + arrow_direction, render_selection); + } + canvas->endPlatformPaint(); +} + +void MenuItemView::DestroyAllMenuHosts() { + if (!HasSubmenu()) + return; + + submenu_->Close(true); + for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; + ++i) { + submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts(); + } +} + +// MenuController ------------------------------------------------------------ + +// static +MenuController* MenuController::active_instance_ = NULL; + +// static +MenuController* MenuController::GetActiveInstance() { + return active_instance_; +} + +#ifdef DEBUG_MENU +static int instance_count = 0; +static int nested_depth = 0; +#endif + +MenuItemView* MenuController::Run(HWND parent, + MenuItemView* root, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition position, + int* result_mouse_event_flags) { + exit_all_ = false; + possible_drag_ = false; + + bool nested_menu = showing_; + if (showing_) { + // Only support nesting of blocking_run menus, nesting of + // blocking/non-blocking shouldn't be needed. + DCHECK(blocking_run_); + + // We're already showing, push the current state. + menu_stack_.push_back(state_); + + // The context menu should be owned by the same parent. + DCHECK(owner_ == parent); + } else { + showing_ = true; + } + + // Reset current state. + pending_state_ = State(); + state_ = State(); + pending_state_.initial_bounds = bounds; + if (bounds.height() > 1) { + // Inset the bounds slightly, otherwise drag coordinates don't line up + // nicely and menus close prematurely. + pending_state_.initial_bounds.Inset(0, 1); + } + pending_state_.anchor = position; + owner_ = parent; + + // Calculate the bounds of the monitor we'll show menus on. Do this once to + // avoid repeated system queries for the info. + POINT initial_loc = { bounds.x(), bounds.y() }; + HMONITOR monitor = MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST); + if (monitor) { + MONITORINFO mi = {0}; + mi.cbSize = sizeof(mi); + GetMonitorInfo(monitor, &mi); + // Menus appear over the taskbar. + pending_state_.monitor_bounds = gfx::Rect(mi.rcMonitor); + } + + did_capture_ = false; + + any_menu_contains_mouse_ = false; + + // Set the selection, which opens the initial menu. + SetSelection(root, true, true); + + if (!blocking_run_) { + // Start the timer to hide the menu. This is needed as we get no + // notification when the drag has finished. + StartCancelAllTimer(); + return NULL; + } + +#ifdef DEBUG_MENU + nested_depth++; + DLOG(INFO) << " entering nested loop, depth=" << nested_depth; +#endif + + MessageLoop::current()->Run(this); + +#ifdef DEBUG_MENU + nested_depth--; + DLOG(INFO) << " exiting nested loop, depth=" << nested_depth; +#endif + + // Close any open menus. + SetSelection(NULL, false, true); + + if (nested_menu) { + DCHECK(!menu_stack_.empty()); + // We're running from within a menu, restore the previous state. + // The menus are already showing, so we don't have to show them. + state_ = menu_stack_.back(); + pending_state_ = menu_stack_.back(); + menu_stack_.pop_back(); + } else { + showing_ = false; + } + + MenuItemView* result = result_; + // In case we're nested, reset result_. + result_ = NULL; + + if (result_mouse_event_flags) + *result_mouse_event_flags = result_mouse_event_flags_; + + if (nested_menu && result) { + // We're nested and about to return a value. The caller might enter another + // blocking loop. We need to make sure all menus are hidden before that + // happens otherwise the menus will stay on screen. + CloseAllNestedMenus(); + + // Set exit_all_ to true, which makes sure all nested loops exit + // immediately. + exit_all_ = true; + } + + return result; +} + +void MenuController::SetSelection(MenuItemView* menu_item, + bool open_submenu, + bool update_immediately) { + size_t paths_differ_at = 0; + std::vector<MenuItemView*> current_path; + std::vector<MenuItemView*> new_path; + BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, + &new_path, &paths_differ_at); + + size_t current_size = current_path.size(); + size_t new_size = new_path.size(); + + // Notify the old path it isn't selected. + for (size_t i = paths_differ_at; i < current_size; ++i) + current_path[i]->SetSelected(false); + + // Notify the new path it is selected. + for (size_t i = paths_differ_at; i < new_size; ++i) + new_path[i]->SetSelected(true); + + pending_state_.item = menu_item; + pending_state_.submenu_open = open_submenu; + + // Stop timers. + StopShowTimer(); + StopCancelAllTimer(); + + if (update_immediately) + CommitPendingSelection(); + else + StartShowTimer(); +} + +void MenuController::Cancel(bool all) { + if (!showing_) { + // This occurs if we're in the process of notifying the delegate for a drop + // and the delegate cancels us. + return; + } + + MenuItemView* selected = state_.item; + exit_all_ = all; + + // Hide windows immediately. + SetSelection(NULL, false, true); + + if (!blocking_run_) { + // If we didn't block the caller we need to notify the menu, which + // triggers deleting us. + DCHECK(selected); + showing_ = false; + selected->GetRootMenuItem()->DropMenuClosed(true); + // WARNING: the call to MenuClosed deletes us. + return; + } +} + +void MenuController::OnMousePressed(SubmenuView* source, + const MouseEvent& event) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnMousePressed source=" << source; +#endif + if (!blocking_run_) + return; + + MenuPart part = + GetMenuPartByScreenCoordinate(source, event.GetX(), event.GetY()); + if (part.is_scroll()) + return; // Ignore presses on scroll buttons. + + if (part.type == MenuPart::NONE || + (part.type == MenuPart::MENU_ITEM && part.menu && + part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { + // Mouse wasn't pressed over any menu, or the active menu, cancel. + + // We're going to close and we own the mouse capture. We need to repost the + // mouse down, otherwise the window the user clicked on won't get the + // event. + RepostEvent(source, event); + + // And close. + Cancel(true); + return; + } + + any_menu_contains_mouse_ = true; + + bool open_submenu = false; + if (!part.menu) { + part.menu = source->GetMenuItem(); + open_submenu = true; + } else { + if (part.menu->GetDelegate()->CanDrag(part.menu)) { + possible_drag_ = true; + press_x_ = event.GetX(); + press_y_ = event.GetY(); + } + if (part.menu->HasSubmenu()) + open_submenu = true; + } + // On a press we immediately commit the selection, that way a submenu + // pops up immediately rather than after a delay. + SetSelection(part.menu, open_submenu, true); +} + +void MenuController::OnMouseDragged(SubmenuView* source, + const MouseEvent& event) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnMouseDragged source=" << source; +#endif + MenuPart part = + GetMenuPartByScreenCoordinate(source, event.GetX(), event.GetY()); + UpdateScrolling(part); + + if (!blocking_run_) + return; + + if (possible_drag_) { + if (ChromeViews::View::ExceededDragThreshold(event.GetX() - press_x_, + event.GetY() - press_y_)) { + MenuItemView* item = state_.item; + DCHECK(item); + // Points are in the coordinates of the submenu, need to map to that of + // the selected item. Additionally source may not be the parent of + // the selected item, so need to map to screen first then to item. + CPoint press_loc(press_x_, press_y_); + View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc); + View::ConvertPointToView(NULL, item, &press_loc); + CPoint drag_loc(event.GetX(), event.GetY()); + View::ConvertPointToScreen(source->GetScrollViewContainer(), &drag_loc); + View::ConvertPointToView(NULL, item, &drag_loc); + in_drag_ = true; + ChromeCanvas canvas(item->GetWidth(), item->GetHeight(), false); + item->Paint(&canvas, true); + + scoped_refptr<OSExchangeData> data(new OSExchangeData); + item->GetDelegate()->WriteDragData(item, data.get()); + drag_utils::SetDragImageOnDataObject(canvas, item->GetWidth(), + item->GetHeight(), press_loc.x, + press_loc.y, data); + + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + int drag_ops = item->GetDelegate()->GetDragOperations(item); + DWORD effects; + StopScrolling(); + DoDragDrop(data, drag_source, + DragDropTypes::DragOperationToDropEffect(drag_ops), + &effects); + if (GetActiveInstance() == this) { + if (showing_ ) { + // We're still showing, close all menus. + CloseAllNestedMenus(); + Cancel(true); + } // else case, drop was on us. + } // else case, someone canceled us, don't do anything + } + return; + } + if (part.type == MenuPart::MENU_ITEM) { + if (!part.menu) + part.menu = source->GetMenuItem(); + SetSelection(part.menu ? part.menu : state_.item, true, false); + } + any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); +} + +void MenuController::OnMouseReleased(SubmenuView* source, + const MouseEvent& event) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnMouseReleased source=" << source; +#endif + if (!blocking_run_) + return; + + DCHECK(state_.item); + possible_drag_ = false; + DCHECK(blocking_run_); + MenuPart part = + GetMenuPartByScreenCoordinate(source, event.GetX(), event.GetY()); + any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); + if (event.IsRightMouseButton() && (part.type == MenuPart::MENU_ITEM && + part.menu)) { + // Set the selection immediately, making sure the submenu is only open + // if it already was. + bool open_submenu = (state_.item == pending_state_.item && + state_.submenu_open); + SetSelection(pending_state_.item, open_submenu, true); + CPoint loc(event.GetX(), event.GetY()); + View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); + part.menu->GetDelegate()->ShowContextMenu( + part.menu, part.menu->GetCommand(), loc.x, loc.y, true); + } else if (!part.is_scroll() && part.menu && !part.menu->HasSubmenu()) { + if (part.menu->GetDelegate()->IsTriggerableEvent(event)) { + Accept(part.menu, event.GetFlags()); + return; + } + } else if (part.type == MenuPart::MENU_ITEM) { + // User either clicked on empty space, or a menu that has children. + SetSelection(part.menu ? part.menu : state_.item, true, true); + } +} + +void MenuController::OnMouseMoved(SubmenuView* source, + const MouseEvent& event) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnMouseMoved source=" << source; +#endif + if (showing_submenu_) + return; + + MenuPart part = + GetMenuPartByScreenCoordinate(source, event.GetX(), event.GetY()); + + UpdateScrolling(part); + + if (!blocking_run_) + return; + + any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); + if (part.type == MenuPart::MENU_ITEM && part.menu) { + SetSelection(part.menu, true, false); + } else if (!part.is_scroll() && any_menu_contains_mouse_ && + pending_state_.item && + (!pending_state_.item->HasSubmenu() || + !pending_state_.item->GetSubmenu()->IsShowing())) { + // On exit if the user hasn't selected an item with a submenu, move the + // selection back to the parent menu item. + SetSelection(pending_state_.item->GetParentMenuItem(), true, false); + any_menu_contains_mouse_ = false; + } +} + +void MenuController::OnMouseEntered(SubmenuView* source, + const MouseEvent& event) { + // MouseEntered is always followed by a mouse moved, so don't need to + // do anything here. +} + +bool MenuController::CanDrop(SubmenuView* source, const OSExchangeData& data) { + return source->GetMenuItem()->GetDelegate()->CanDrop(source->GetMenuItem(), + data); +} + +void MenuController::OnDragEntered(SubmenuView* source, + const DropTargetEvent& event) { + valid_drop_coordinates_ = false; +} + +int MenuController::OnDragUpdated(SubmenuView* source, + const DropTargetEvent& event) { + StopCancelAllTimer(); + + CPoint screen_loc(event.GetX(), event.GetY()); + View::ConvertPointToScreen(source, &screen_loc); + if (valid_drop_coordinates_ && screen_loc.x == drop_x_ && + screen_loc.y == drop_y_) { + return last_drop_operation_; + } + drop_x_ = screen_loc.x; + drop_y_ = screen_loc.y; + valid_drop_coordinates_ = true; + + MenuItemView* menu_item = GetMenuItemAt(source, event.GetX(), event.GetY()); + bool over_empty_menu = false; + if (!menu_item) { + // See if we're over an empty menu. + menu_item = GetEmptyMenuItemAt(source, event.GetX(), event.GetY()); + if (menu_item) + over_empty_menu = true; + } + MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE; + int drop_operation = DragDropTypes::DRAG_NONE; + if (menu_item) { + CPoint menu_item_loc(event.GetX(), event.GetY()); + View::ConvertPointToView(source, menu_item, &menu_item_loc); + MenuItemView* query_menu_item; + if (!over_empty_menu) { + int menu_item_height = menu_item->GetHeight(); + if (menu_item->HasSubmenu() && + (menu_item_loc.y > MenuItemView::kDropBetweenPixels && + menu_item_loc.y < (menu_item_height - + MenuItemView::kDropBetweenPixels))) { + drop_position = MenuDelegate::DROP_ON; + } else if (menu_item_loc.y < menu_item_height / 2) { + drop_position = MenuDelegate::DROP_BEFORE; + } else { + drop_position = MenuDelegate::DROP_AFTER; + } + query_menu_item = menu_item; + } else { + query_menu_item = menu_item->GetParentMenuItem(); + drop_position = MenuDelegate::DROP_ON; + } + drop_operation = menu_item->GetDelegate()->GetDropOperation( + query_menu_item, event, &drop_position); + + if (menu_item->HasSubmenu()) { + // The menu has a submenu, schedule the submenu to open. + SetSelection(menu_item, true, false); + } else { + SetSelection(menu_item, false, false); + } + + if (drop_position == MenuDelegate::DROP_NONE || + drop_operation == DragDropTypes::DRAG_NONE) { + menu_item = NULL; + } + } else { + SetSelection(source->GetMenuItem(), true, false); + } + SetDropMenuItem(menu_item, drop_position); + last_drop_operation_ = drop_operation; + return drop_operation; +} + +void MenuController::OnDragExited(SubmenuView* source) { + StartCancelAllTimer(); + + if (drop_target_) { + StopShowTimer(); + SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); + } +} + +int MenuController::OnPerformDrop(SubmenuView* source, + const DropTargetEvent& event) { + DCHECK(drop_target_); + // NOTE: the delegate may delete us after invoking OnPerformDrop, as such + // we don't call cancel here. + + MenuItemView* item = state_.item; + DCHECK(item); + + MenuItemView* drop_target = drop_target_; + MenuDelegate::DropPosition drop_position = drop_position_; + + // Close all menus, including any nested menus. + SetSelection(NULL, false, true); + CloseAllNestedMenus(); + + // Set state such that we exit. + showing_ = false; + exit_all_ = true; + + if (!IsBlockingRun()) + item->GetRootMenuItem()->DropMenuClosed(false); + + // WARNING: the call to MenuClosed deletes us. + + // If over an empty menu item, drop occurs on the parent. + if (drop_target->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) + drop_target = drop_target->GetParentMenuItem(); + + return drop_target->GetDelegate()->OnPerformDrop( + drop_target, drop_position, event); +} + +void MenuController::OnDragEnteredScrollButton(SubmenuView* source, + bool is_up) { + MenuPart part; + part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN; + part.submenu = source; + UpdateScrolling(part); + + // Do this to force the selection to hide. + SetDropMenuItem(source->GetMenuItemAt(0), MenuDelegate::DROP_NONE); + + StopCancelAllTimer(); +} + +void MenuController::OnDragExitedScrollButton(SubmenuView* source) { + StartCancelAllTimer(); + SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); + StopScrolling(); +} + +// static +void MenuController::SetActiveInstance(MenuController* controller) { + active_instance_ = controller; +} + +bool MenuController::Dispatch(const MSG& msg) { + DCHECK(blocking_run_); + + if (exit_all_) + return false; + + // NOTE: we don't get WM_ACTIVATE or anything else interesting in here. + switch (msg.message) { + // NOTE: focus wasn't changed when the menu was shown. As such, don't + // dispatch key events otherwise the focused window will get the events. + case WM_KEYDOWN: + return OnKeyDown(msg); + + case WM_CHAR: + return OnChar(msg); + + case WM_KEYUP: + return true; + + case WM_CANCELMODE: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + // Exit immediately on system keys. + Cancel(true); + return false; + + default: + break; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + return !exit_all_; +} + +bool MenuController::OnKeyDown(const MSG& msg) { + DCHECK(blocking_run_); + + switch (msg.wParam) { + case VK_UP: + IncrementSelection(-1); + break; + + case VK_DOWN: + IncrementSelection(1); + break; + + // Handling of VK_RIGHT and VK_LEFT is different depending on the UI + // layout. + case VK_RIGHT: + if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT) + CloseSubmenu(); + else + OpenSubmenuChangeSelectionIfCan(); + break; + + case VK_LEFT: + if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT) + OpenSubmenuChangeSelectionIfCan(); + else + CloseSubmenu(); + break; + + case VK_RETURN: + if (pending_state_.item) { + if (pending_state_.item->HasSubmenu()) { + OpenSubmenuChangeSelectionIfCan(); + } else if (pending_state_.item->IsEnabled()) { + Accept(pending_state_.item, 0); + return false; + } + } + break; + + case VK_ESCAPE: + if (!state_.item->GetParentMenuItem() || + (!state_.item->GetParentMenuItem()->GetParentMenuItem() && + (!state_.item->HasSubmenu() || + !state_.item->GetSubmenu()->IsShowing()))) { + // User pressed escape and only one menu is shown, cancel it. + Cancel(false); + return false; + } else { + CloseSubmenu(); + } + break; + + default: + TranslateMessage(&msg); + break; + } + return true; +} + +bool MenuController::OnChar(const MSG& msg) { + DCHECK(blocking_run_); + + return !SelectByChar(static_cast<wchar_t>(msg.wParam)); +} + +MenuController::MenuController(bool blocking) + : blocking_run_(blocking), + showing_(false), + exit_all_(false), + did_capture_(false), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + show_task_(this), + result_(NULL), + show_timer_(NULL), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + cancel_all_task_(this), + cancel_all_timer_(NULL), + drop_target_(NULL), + owner_(NULL), + possible_drag_(false), + valid_drop_coordinates_(false), + in_drag_(false), + any_menu_contains_mouse_(false), + showing_submenu_(false), + result_mouse_event_flags_(0) { +#ifdef DEBUG_MENU + instance_count++; + DLOG(INFO) << "created MC, count=" << instance_count; +#endif +} + +MenuController::~MenuController() { + DCHECK(!showing_); + StopShowTimer(); + StopCancelAllTimer(); +#ifdef DEBUG_MENU + instance_count--; + DLOG(INFO) << "destroyed MC, count=" << instance_count; +#endif +} + +void MenuController::Accept(MenuItemView* item, int mouse_event_flags) { + DCHECK(IsBlockingRun()); + result_ = item; + exit_all_ = true; + result_mouse_event_flags_ = mouse_event_flags; +} + +void MenuController::CloseAllNestedMenus() { + for (std::list<State>::iterator i = menu_stack_.begin(); + i != menu_stack_.end(); ++i) { + MenuItemView* item = i->item; + MenuItemView* last_item = item; + while (item) { + CloseMenu(item); + last_item = item; + item = item->GetParentMenuItem(); + } + i->submenu_open = false; + i->item = last_item; + } +} + +MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { + View* child_under_mouse = source->GetViewForPoint(CPoint(x, y)); + if (child_under_mouse && child_under_mouse->IsEnabled() && + child_under_mouse->GetID() == MenuItemView::kMenuItemViewID) { + return static_cast<MenuItemView*>(child_under_mouse); + } + return NULL; +} + +MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { + View* child_under_mouse = source->GetViewForPoint(CPoint(x, y)); + if (child_under_mouse && + child_under_mouse->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) { + return static_cast<MenuItemView*>(child_under_mouse); + } + return NULL; +} + +bool MenuController::IsScrollButtonAt(SubmenuView* source, + int x, + int y, + MenuPart::Type* part) { + MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer(); + View* child_under_mouse = scroll_view->GetViewForPoint(CPoint(x, y)); + if (child_under_mouse && child_under_mouse->IsEnabled()) { + if (child_under_mouse == scroll_view->scroll_up_button()) { + *part = MenuPart::SCROLL_UP; + return true; + } + if (child_under_mouse == scroll_view->scroll_down_button()) { + *part = MenuPart::SCROLL_DOWN; + return true; + } + } + return false; +} + +MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinate( + SubmenuView* source, + int source_x, + int source_y) { + MenuPart part; + + CPoint screen_loc(source_x, source_y); + View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); + + MenuItemView* item = state_.item; + while (item) { + if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && + GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc, + &part)) { + return part; + } + item = item->GetParentMenuItem(); + } + + return part; +} + +bool MenuController::GetMenuPartByScreenCoordinateImpl( + SubmenuView* menu, + const CPoint& screen_loc, + MenuPart* part) { + // Is the mouse over the scroll buttons? + CPoint scroll_view_loc = screen_loc; + View* scroll_view_container = menu->GetScrollViewContainer(); + View::ConvertPointToView(NULL, scroll_view_container, &scroll_view_loc); + if (scroll_view_loc.x < 0 || + scroll_view_loc.x >= scroll_view_container->GetWidth() || + scroll_view_loc.y < 0 || + scroll_view_loc.y >= scroll_view_container->GetHeight()) { + // Point isn't contained in menu. + return false; + } + if (IsScrollButtonAt(menu, scroll_view_loc.x, scroll_view_loc.y, + &(part->type))) { + part->submenu = menu; + return true; + } + + // Not over the scroll button. Check the actual menu. + if (DoesSubmenuContainLocation(menu, screen_loc)) { + CPoint menu_loc = screen_loc; + View::ConvertPointToView(NULL, menu, &menu_loc); + part->menu = GetMenuItemAt(menu, menu_loc.x, menu_loc.y); + part->type = MenuPart::MENU_ITEM; + return true; + } + + // While the mouse isn't over a menu item or the scroll buttons of menu, it + // is contained by menu and so we return true. If we didn't return true other + // menus would be searched, even though they are likely obscured by us. + return true; +} + +bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu, + const CPoint& screen_loc) { + CPoint view_loc = screen_loc; + View::ConvertPointToView(NULL, submenu, &view_loc); + gfx::Rect vis_rect = submenu->GetVisibleBounds(); + return vis_rect.Contains(view_loc.x, view_loc.y); +} + +void MenuController::CommitPendingSelection() { + StopShowTimer(); + + size_t paths_differ_at = 0; + std::vector<MenuItemView*> current_path; + std::vector<MenuItemView*> new_path; + BuildPathsAndCalculateDiff(state_.item, pending_state_.item, ¤t_path, + &new_path, &paths_differ_at); + + // Hide the old menu. + for (size_t i = paths_differ_at; i < current_path.size(); ++i) { + if (current_path[i]->HasSubmenu()) { + // See description of in_drag_ as to why close is conditionalized like + // this. + current_path[i]->GetSubmenu()->Close(!in_drag_); + } + } + + // Copy pending to state_, making sure to preserve the direction menus were + // opened. + std::list<bool> pending_open_direction; + state_.open_leading.swap(pending_open_direction); + state_ = pending_state_; + state_.open_leading.swap(pending_open_direction); + + int menu_depth = MenuDepth(state_.item); + if (menu_depth == 0) { + state_.open_leading.clear(); + } else { + int cached_size = static_cast<int>(state_.open_leading.size()); + DCHECK(menu_depth >= 0); + while (cached_size-- >= menu_depth) + state_.open_leading.pop_back(); + } + + if (!state_.item) { + // Nothing to select. + StopScrolling(); + return; + } + + // Open all the submenus preceeding the last menu item (last menu item is + // handled next). + if (new_path.size() > 1) { + for (std::vector<MenuItemView*>::iterator i = new_path.begin(); + i != new_path.end() - 1; ++i) { + OpenMenu(*i); + } + } + + if (state_.submenu_open) { + // The submenu should be open, open the submenu if the item has a submenu. + if (state_.item->HasSubmenu()) { + OpenMenu(state_.item); + } else { + state_.submenu_open = false; + } + } else if (state_.item->HasSubmenu() && + state_.item->GetSubmenu()->IsShowing()) { + // The submenu is showing, but it shouldn't be, close it. + // See description of in_drag_ as to why close is conditionalized like + // this. + state_.item->GetSubmenu()->Close(!in_drag_); + } + + if (scroll_task_.get() && scroll_task_->submenu()) { + // Stop the scrolling if none of the elements of the selection contain + // the menu being scrolled. + bool found = false; + MenuItemView* item = state_.item; + while (item && !found) { + found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && + item->GetSubmenu() == scroll_task_->submenu()); + item = item->GetParentMenuItem(); + } + if (!found) + StopScrolling(); + } +} + +void MenuController::CloseMenu(MenuItemView* item) { + DCHECK(item); + if (!item->HasSubmenu()) + return; + // See description of in_drag_ as to why close is conditionalized like this. + item->GetSubmenu()->Close(!in_drag_); +} + +void MenuController::OpenMenu(MenuItemView* item) { + DCHECK(item); + if (item->GetSubmenu()->IsShowing()) { + return; + } + + bool prefer_leading = + state_.open_leading.empty() ? true : state_.open_leading.back(); + bool resulting_direction; + gfx::Rect bounds = + CalculateMenuBounds(item, prefer_leading, &resulting_direction); + state_.open_leading.push_back(resulting_direction); + bool do_capture = (!did_capture_ && blocking_run_); + showing_submenu_ = true; + item->GetSubmenu()->ShowAt(owner_, bounds, do_capture); + showing_submenu_ = false; + did_capture_ = true; +} + +void MenuController::BuildPathsAndCalculateDiff( + MenuItemView* old_item, + MenuItemView* new_item, + std::vector<MenuItemView*>* old_path, + std::vector<MenuItemView*>* new_path, + size_t* first_diff_at) { + DCHECK(old_path && new_path && first_diff_at); + BuildMenuItemPath(old_item, old_path); + BuildMenuItemPath(new_item, new_path); + + size_t common_size = std::min(old_path->size(), new_path->size()); + + // Find the first difference between the two paths, when the loop + // returns, diff_i is the first index where the two paths differ. + for (size_t i = 0; i < common_size; ++i) { + if ((*old_path)[i] != (*new_path)[i]) { + *first_diff_at = i; + return; + } + } + + *first_diff_at = common_size; +} + +void MenuController::BuildMenuItemPath(MenuItemView* item, + std::vector<MenuItemView*>* path) { + if (!item) + return; + BuildMenuItemPath(item->GetParentMenuItem(), path); + path->push_back(item); +} + +void MenuController::StartShowTimer() { + StopShowTimer(); + show_timer_ = MessageLoop::current()->timer_manager()-> + StartTimer(kShowDelay, &show_task_, false); +} + +void MenuController::StopShowTimer() { + if (show_timer_) { + MessageLoop::current()->timer_manager()->StopTimer(show_timer_); + delete show_timer_; + show_timer_ = NULL; + } +} + +void MenuController::StartCancelAllTimer() { + StopCancelAllTimer(); + cancel_all_timer_ = MessageLoop::current()->timer_manager()-> + StartTimer(kCloseOnExitTime, &cancel_all_task_, false); +} + +void MenuController::StopCancelAllTimer() { + if (cancel_all_timer_) { + MessageLoop::current()->timer_manager()->StopTimer(cancel_all_timer_); + delete cancel_all_timer_; + cancel_all_timer_ = NULL; + } +} + +gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, + bool prefer_leading, + bool* is_leading) { + DCHECK(item); + + SubmenuView* submenu = item->GetSubmenu(); + DCHECK(submenu); + + CSize pref; + submenu->GetScrollViewContainer()->GetPreferredSize(&pref); + + // Don't let the menu go to wide. This is some where between what IE and FF + // do. + pref.cx = std::min(pref.cx, kMaxMenuWidth); + if (!state_.monitor_bounds.IsEmpty()) + pref.cx = std::min(pref.cx, + static_cast<LONG>(state_.monitor_bounds.width())); + + // Assume we can honor prefer_leading. + *is_leading = prefer_leading; + + int x, y; + + if (!item->GetParentMenuItem()) { + // First item, position relative to initial location. + x = state_.initial_bounds.x(); + y = state_.initial_bounds.bottom(); + if (state_.anchor == MenuItemView::TOPRIGHT) + x = x + state_.initial_bounds.width() - pref.cx; + if (!state_.monitor_bounds.IsEmpty() && + y + pref.cy > state_.monitor_bounds.bottom()) { + // The menu doesn't fit on screen. If the first location is above the + // half way point, show from the mouse location to bottom of screen. + // Otherwise show from the top of the screen to the location of the mouse. + // While odd, this behavior matches IE. + if (y < (state_.monitor_bounds.y() + + state_.monitor_bounds.height() / 2)) { + pref.cy = std::min(pref.cy, + static_cast<LONG>(state_.monitor_bounds.bottom() - y)); + } else { + pref.cy = std::min(pref.cy, static_cast<LONG>( + state_.initial_bounds.y() - state_.monitor_bounds.y())); + y = state_.initial_bounds.y() - pref.cy; + } + } + } else { + // Not the first menu; position it relative to the bounds of the menu + // item. + CPoint item_loc(0, 0); + View::ConvertPointToScreen(item, &item_loc); + + // We must make sure we take into account the UI layout. If the layout is + // RTL, then a 'leading' menu is positioned to the left of the parent menu + // item and not to the right. + bool layout_is_rtl = item->UILayoutIsRightToLeft(); + bool create_on_the_right = (prefer_leading && !layout_is_rtl) || + (!prefer_leading && layout_is_rtl); + + if (create_on_the_right) { + x = item_loc.x + item->GetWidth() - kSubmenuHorizontalInset; + if (state_.monitor_bounds.width() != 0 && + x + pref.cx > state_.monitor_bounds.right()) { + if (layout_is_rtl) + *is_leading = true; + else + *is_leading = false; + x = item_loc.x - pref.cx + kSubmenuHorizontalInset; + } + } else { + x = item_loc.x - pref.cx + kSubmenuHorizontalInset; + if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { + if (layout_is_rtl) + *is_leading = false; + else + *is_leading = true; + x = item_loc.x + item->GetWidth() - kSubmenuHorizontalInset; + } + } + y = item_loc.y - kSubmenuBorderSize; + if (state_.monitor_bounds.width() != 0) { + pref.cy = std::min(pref.cy, + static_cast<LONG>(state_.monitor_bounds.height())); + if (y + pref.cy > state_.monitor_bounds.bottom()) + y = state_.monitor_bounds.bottom() - pref.cy; + if (y < state_.monitor_bounds.y()) + y = state_.monitor_bounds.y(); + } + } + + if (state_.monitor_bounds.width() != 0) { + if (x + pref.cx > state_.monitor_bounds.right()) + x = state_.monitor_bounds.right() - pref.cx; + if (x < state_.monitor_bounds.x()) + x = state_.monitor_bounds.x(); + } + return gfx::Rect(x, y, pref.cx, pref.cy); +} + +// static +int MenuController::MenuDepth(MenuItemView* item) { + if (!item) + return 0; + return MenuDepth(item->GetParentMenuItem()) + 1; +} + +void MenuController::IncrementSelection(int delta) { + MenuItemView* item = pending_state_.item; + DCHECK(item); + if (pending_state_.submenu_open && item->HasSubmenu() && + item->GetSubmenu()->IsShowing()) { + // A menu is selected and open, but none of its children are selected, + // select the first menu item. + if (item->GetSubmenu()->GetMenuItemCount()) { + SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, false); + ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0)); + return; // return so else case can fall through. + } + } + if (item->GetParentMenuItem()) { + MenuItemView* parent = item->GetParentMenuItem(); + int parent_count = parent->GetSubmenu()->GetMenuItemCount(); + if (parent_count > 1) { + for (int i = 0; i < parent_count; ++i) { + if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { + int next_index = (i + delta + parent_count) % parent_count; + ScrollToVisible(parent->GetSubmenu()->GetMenuItemAt(next_index)); + SetSelection(parent->GetSubmenu()->GetMenuItemAt(next_index), false, + false); + break; + } + } + } + } +} + +void MenuController::OpenSubmenuChangeSelectionIfCan() { + MenuItemView* item = pending_state_.item; + if (item->HasSubmenu()) { + if (item->GetSubmenu()->GetMenuItemCount() > 0) { + SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, true); + } else { + // No menu items, just show the sub-menu. + SetSelection(item, true, true); + } + } +} + +void MenuController::CloseSubmenu() { + MenuItemView* item = state_.item; + DCHECK(item); + if (!item->GetParentMenuItem()) + return; + if (item->HasSubmenu() && item->GetSubmenu()->IsShowing()) { + SetSelection(item, false, true); + } else if (item->GetParentMenuItem()->GetParentMenuItem()) { + SetSelection(item->GetParentMenuItem(), false, true); + } +} + +bool MenuController::IsMenuWindow(MenuItemView* item, HWND window) { + if (!item) + return false; + return ((item->HasSubmenu() && item->GetSubmenu()->IsShowing() && + item->GetSubmenu()->GetViewContainer()->GetHWND() == window) || + IsMenuWindow(item->GetParentMenuItem(), window)); +} + +bool MenuController::SelectByChar(wchar_t character) { + wchar_t char_array[1] = { character }; + wchar_t key = l10n_util::ToLower(char_array)[0]; + MenuItemView* item = pending_state_.item; + if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing()) + item = item->GetParentMenuItem(); + DCHECK(item); + DCHECK(item->HasSubmenu()); + SubmenuView* submenu = item->GetSubmenu(); + DCHECK(submenu); + int menu_item_count = submenu->GetMenuItemCount(); + if (!menu_item_count) + return false; + for (int i = 0; i < menu_item_count; ++i) { + MenuItemView* child = submenu->GetMenuItemAt(i); + if (child->GetMnemonic() == key && child->IsEnabled()) { + Accept(child, 0); + return true; + } + } + + // No matching mnemonic, search through items that don't have mnemonic + // based on first character of the title. + int first_match = -1; + bool has_multiple = false; + int next_match = -1; + int index_of_item = -1; + for (int i = 0; i < menu_item_count; ++i) { + MenuItemView* child = submenu->GetMenuItemAt(i); + if (!child->GetMnemonic() && child->IsEnabled()) { + std::wstring lower_title = l10n_util::ToLower(child->GetTitle()); + if (child == pending_state_.item) + index_of_item = i; + if (lower_title.length() && lower_title[0] == key) { + if (first_match == -1) + first_match = i; + else + has_multiple = true; + if (next_match == -1 && index_of_item != -1 && i > index_of_item) + next_match = i; + } + } + } + if (first_match != -1) { + if (!has_multiple) { + if (submenu->GetMenuItemAt(first_match)->HasSubmenu()) { + SetSelection(submenu->GetMenuItemAt(first_match), true, false); + } else { + Accept(submenu->GetMenuItemAt(first_match), 0); + return true; + } + } else if (index_of_item == -1 || next_match == -1) { + SetSelection(submenu->GetMenuItemAt(first_match), false, false); + } else { + SetSelection(submenu->GetMenuItemAt(next_match), false, false); + } + } + return false; +} + +void MenuController::RepostEvent(SubmenuView* source, + const MouseEvent& event) { + CPoint screen_loc(event.GetX(), event.GetY()); + View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); + HWND window = WindowFromPoint(screen_loc); + if (window) { +#ifdef DEBUG_MENU + DLOG(INFO) << "RepostEvent on press"; +#endif + + // Release the capture. + SubmenuView* submenu = state_.item->GetRootMenuItem()->GetSubmenu(); + submenu->ReleaseCapture(); + + if (submenu->host() && submenu->host()->GetHWND() && + GetWindowThreadProcessId(submenu->host()->GetHWND(), NULL) != + GetWindowThreadProcessId(window, NULL)) { + // Even though we have mouse capture, windows generates a mouse event + // if the other window is in a separate thread. Don't generate an event in + // this case else the target window can get double events leading to bad + // behavior. + return; + } + + // Convert the coordinates to the target window. + RECT window_bounds; + GetWindowRect(window, &window_bounds); + int window_x = screen_loc.x - window_bounds.left; + int window_y = screen_loc.y - window_bounds.top; + + // Determine whether the click was in the client area or not. + // NOTE: WM_NCHITTEST coordinates are relative to the screen. + LRESULT nc_hit_result = SendMessage(window, WM_NCHITTEST, 0, + MAKELPARAM(screen_loc.x, screen_loc.y)); + const bool in_client_area = (nc_hit_result == HTCLIENT); + + // TODO(sky): this isn't right. The event to generate should correspond + // with the event we just got. MouseEvent only tells us what is down, + // which may differ. Need to add ability to get changed button from + // MouseEvent. + int event_type; + if (event.IsLeftMouseButton()) + event_type = in_client_area ? WM_LBUTTONDOWN : WM_NCLBUTTONDOWN; + else if (event.IsMiddleMouseButton()) + event_type = in_client_area ? WM_MBUTTONDOWN : WM_NCMBUTTONDOWN; + else if (event.IsRightMouseButton()) + event_type = in_client_area ? WM_RBUTTONDOWN : WM_NCRBUTTONDOWN; + else + event_type = 0; // Unknown mouse press. + + if (event_type) { + if (in_client_area) { + PostMessage(window, event_type, event.GetWindowsFlags(), + MAKELPARAM(window_x, window_y)); + } else { + PostMessage(window, WM_NCLBUTTONDOWN, nc_hit_result, + MAKELPARAM(window_x, window_y)); + } + } + } +} + +void MenuController::SetDropMenuItem( + MenuItemView* new_target, + MenuDelegate::DropPosition new_position) { + if (new_target == drop_target_ && new_position == drop_position_) + return; + + if (drop_target_) { + drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( + NULL, MenuDelegate::DROP_NONE); + } + drop_target_ = new_target; + drop_position_ = new_position; + if (drop_target_) { + drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( + drop_target_, drop_position_); + } +} + +void MenuController::UpdateScrolling(const MenuPart& part) { + if (!part.is_scroll() && !scroll_task_.get()) + return; + + if (!scroll_task_.get()) + scroll_task_.reset(new MenuScrollTask()); + scroll_task_->Update(part); +} + +void MenuController::StopScrolling() { + scroll_task_.reset(NULL); +} + +} // namespace ChromeViews diff --git a/chrome/views/chrome_menu.h b/chrome/views/chrome_menu.h new file mode 100644 index 0000000..7a1d15b --- /dev/null +++ b/chrome/views/chrome_menu.h @@ -0,0 +1,990 @@ +// 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. + +#ifndef CHROME_VIEWS_CHROME_MENU_H__ +#define CHROME_VIEWS_CHROME_MENU_H__ + +#include <list> + +#include "base/gfx/point.h" +#include "base/gfx/rect.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/controller.h" +#include "chrome/views/view.h" +#include "skia/include/SkBitmap.h" + +class Timer; + +namespace ChromeViews { + +class HWNDViewContainer; +class MenuController; +class MenuItemView; +class SubmenuView; + +namespace { +class MenuHost; +class MenuHostRootView; +class MenuScrollTask; +class MenuScrollViewContainer; +} + +// MenuDelegate -------------------------------------------------------------- + +// Delegate for the menu. + +class MenuDelegate : Controller { + public: + // Used during drag and drop to indicate where the drop indicator should + // be rendered. + enum DropPosition { + // Indicates a drop is not allowed here. + DROP_NONE, + + // Indicates the drop should occur before the item. + DROP_BEFORE, + + // Indicates the drop should occur after the item. + DROP_AFTER, + + // Indicates the drop should occur on the item. + DROP_ON + }; + + // Whether or not an item should be shown as checked. + // TODO(sky): need checked support. + virtual bool IsItemChecked(int id) const { + return false; + } + + // The string shown for the menu item. This is only invoked when an item is + // added with an empty label. + virtual std::wstring GetLabel(int id) const { + return std::wstring(); + } + + // Shows the context menu with the specified id. This is invoked when the + // user does the appropriate gesture to show a context menu. The id + // identifies the id of the menu to show the context menu for. + // is_mouse_gesture is true if this is the result of a mouse gesture. + // If this is not the result of a mouse gesture x/y is the recommended + // location to display the content menu at. In either case, x/y is in + // screen coordinates. + virtual void ShowContextMenu(MenuItemView* source, + int id, + int x, + int y, + bool is_mouse_gesture) { + } + + // Controller + virtual bool SupportsCommand(int id) const { + return true; + } + virtual bool IsCommandEnabled(int id) const { + return true; + } + virtual bool GetContextualLabel(int id, std::wstring* out) const { + return false; + } + virtual void ExecuteCommand(int id) { + } + + // Executes the specified command. mouse_event_flags give the flags of the + // mouse event that triggered this to be invoked (ChromeViews::MouseEvent + // flags). mouse_event_flags is 0 if this is triggered by a user gesture + // other than a mouse event. + virtual void ExecuteCommand(int id, int mouse_event_flags) { + ExecuteCommand(id); + } + + // Returns true if the specified mouse event is one the user can use + // to trigger, or accept, the mouse. Defaults to only left mouse buttons. + virtual bool IsTriggerableEvent(const MouseEvent& e) { + return e.IsLeftMouseButton(); + } + + // Invoked to determine if drops can be accepted for a submenu. This is + // ONLY invoked for menus that have submenus and indicates whether or not + // a drop can occur on any of the child items of the item. For example, + // consider the following menu structure: + // + // A + // B + // C + // + // Where A has a submenu with children B and C. This is ONLY invoked for + // A, not B and C. + // + // To restrict which children can be dropped on override GetDropOperation. + virtual bool CanDrop(MenuItemView* menu, const OSExchangeData& data) { + return false; + } + + // Returns the drop operation for the specified target menu item. This is + // only invoked if CanDrop returned true for the parent menu. position + // is set based on the location of the mouse, reset to specify a different + // position. + // + // If a drop should not be allowed, returned DragDropTypes::DRAG_NONE. + virtual int GetDropOperation(MenuItemView* item, + const DropTargetEvent& event, + DropPosition* position) { + NOTREACHED() << "If you override CanDrop, you need to override this too"; + return DragDropTypes::DRAG_NONE; + } + + // Invoked to perform the drop operation. This is ONLY invoked if + // canDrop returned true for the parent menu item, and GetDropOperation + // returned an operation other than DragDropTypes::DRAG_NONE. + // + // menu indicates the menu the drop occurred on. + virtual int OnPerformDrop(MenuItemView* menu, + DropPosition position, + const DropTargetEvent& event) { + NOTREACHED() << "If you override CanDrop, you need to override this too"; + return DragDropTypes::DRAG_NONE; + } + + // Invoked to determine if it is possible for the user to drag the specified + // menu item. + virtual bool CanDrag(MenuItemView* menu) { + return false; + } + + // Invoked to write the data for a drag operation to data. sender is the + // MenuItemView being dragged. + virtual void WriteDragData(MenuItemView* sender, OSExchangeData* data) { + NOTREACHED() << "If you override CanDrag, you must override this too."; + } + + // Invoked to determine the drag operations for a drag session of sender. + // See DragDropTypes for possible values. + virtual int GetDragOperations(MenuItemView* sender) { + NOTREACHED() << "If you override CanDrag, you must override this too."; + return 0; + } + + // Notification the menu has closed. This is only sent when running the + // menu for a drop. + virtual void DropMenuClosed(MenuItemView* menu) { + } +}; + +// MenuItemView -------------------------------------------------------------- + +// MenuItemView represents a single menu item with a label and optional icon. +// Each MenuItemView may also contain a submenu, which in turn may contain +// any number of child MenuItemViews. +// +// To use a menu create an initial MenuItemView using the constructor that +// takes a MenuDelegate, then create any number of child menu items by way +// of the various AddXXX methods. +// +// MenuItemView is itself a View, which means you can add Views to each +// MenuItemView. This normally NOT want you want, rather add other child Views +// to the submenu of the MenuItemView. +// +// There are two ways to show a MenuItemView: +// 1. Use RunMenuAt. This blocks the caller, executing the selected command +// on success. +// 2. Use RunMenuForDropAt. This is intended for use during a drop session +// and does NOT block the caller. Instead the delegate is notified when the +// menu closes via the DropMenuClosed method. + +class MenuItemView : public View { + friend class MenuController; + + public: + // ID used to identify menu items. + static const int kMenuItemViewID; + + // Used to determine whether a drop is on an item or before/after it. If + // a drop occurs kDropBetweenPixels from the top/bottom it is considered + // before/after the menu item, otherwise it is on the item. + static const int kDropBetweenPixels; + + // Different types of menu items. + enum Type { + NORMAL, + SUBMENU, + CHECKBOX, + RADIO, + SEPARATOR + }; + + // Where the menu should be anchored to. + enum AnchorPosition { + TOPLEFT, + TOPRIGHT + }; + + // Constructor for use with the top level menu item. This menu is never + // shown to the user, rather its use as the parent for all menu items. + explicit MenuItemView(MenuDelegate* delegate); + + virtual ~MenuItemView(); + + // Run methods. See description above class for details. Both Run methods take + // a rectangle, which is used to position the menu. + void RunMenuAt(HWND parent, + const gfx::Rect& bounds, + AnchorPosition anchor, + bool show_mnemonics); + void RunMenuForDropAt(HWND parent, + const gfx::Rect& bounds, + AnchorPosition anchor); + + // Hides and cancels the menu. This does nothing if the menu is not open. + void Cancel(); + + // Adds an item to this menu. + // item_id The id of the item, used to identify it in delegate callbacks + // or (if delegate is NULL) to identify the command associated + // with this item with the controller specified in the ctor. Note + // that this value should not be 0 as this has a special meaning + // ("NULL command, no item selected") + // label The text label shown. + // type The type of item. + void AppendMenuItem(int item_id, + const std::wstring& label, + Type type) { + AppendMenuItemInternal(item_id, label, SkBitmap(), type); + } + + // Append a submenu to this menu. + // The returned pointer is owned by this menu. + MenuItemView* AppendSubMenu(int item_id, + const std::wstring& label) { + return AppendMenuItemInternal(item_id, label, SkBitmap(), SUBMENU); + } + + // Append a submenu with an icon to this menu. + // The returned pointer is owned by this menu. + MenuItemView* AppendSubMenuWithIcon(int item_id, + const std::wstring& label, + const SkBitmap& icon) { + return AppendMenuItemInternal(item_id, label, icon, SUBMENU); + } + + // This is a convenience for standard text label menu items where the label + // is provided with this call. + void AppendMenuItemWithLabel(int item_id, + const std::wstring& label) { + AppendMenuItem(item_id, label, NORMAL); + } + + // This is a convenience for text label menu items where the label is + // provided by the delegate. + void AppendDelegateMenuItem(int item_id) { + AppendMenuItem(item_id, std::wstring(), NORMAL); + } + + // Adds a separator to this menu + void AppendSeparator() { + AppendMenuItemInternal(0, std::wstring(), SkBitmap(), SEPARATOR); + } + + // Appends a menu item with an icon. This is for the menu item which + // needs an icon. Calling this function forces the Menu class to draw + // the menu, instead of relying on Windows. + void AppendMenuItemWithIcon(int item_id, + const std::wstring& label, + const SkBitmap& icon) { + AppendMenuItemInternal(item_id, label, icon, NORMAL); + } + + // Returns the view that contains child menu items. If the submenu has + // not been creates, this creates it. + virtual SubmenuView* CreateSubmenu(); + + // Returns true if this menu item has a submenu. + virtual bool HasSubmenu() const { return (submenu_ != NULL); } + + // Returns the view containing child menu items. + virtual SubmenuView* GetSubmenu() const { return submenu_; } + + // Returns the parent menu item. + MenuItemView* GetParentMenuItem() const { return parent_menu_item_; } + + // Sets the font. + void SetFont(const ChromeFont& font) { font_ = font; } + + // Sets the title + void SetTitle(const std::wstring& title) { + title_ = title; + } + + // Returns the title. + const std::wstring& GetTitle() const { return title_; } + + // Sets whether this item is selected. This is invoked as the user moves + // the mouse around the menu while open. + void SetSelected(bool selected); + + // Returns true if the item is selected. + bool IsSelected() const { return selected_; } + + // Sets the icon for the descendant identified by item_id. + void SetIcon(const SkBitmap& icon, int item_id); + + // Sets the icon of this menu item. + void SetIcon(const SkBitmap& icon); + + // Returns the icon. + const SkBitmap& GetIcon() const { return icon_; } + + // Sets the command id of this menu item. + void SetCommand(int command) { command_ = command; } + + // Returns the command id of this item. + int GetCommand() const { return command_; } + + // Paints the menu item. + virtual void Paint(ChromeCanvas* canvas); + + // Returns the preferred size of this item. + virtual void GetPreferredSize(CSize* out); + + // Returns the object responsible for controlling showing the menu. + MenuController* GetMenuController(); + + // Returns the delegate. This returns the delegate of the root menu item. + MenuDelegate* GetDelegate(); + + // Returns the root parent, or this if this has no parent. + MenuItemView* GetRootMenuItem(); + + // Returs the mnemonic for this MenuItemView, or 0 if this MenuItemView + // doesn't have a mnemonic. + wchar_t GetMnemonic(); + + protected: + // Creates a MenuItemView. This is used by the various AddXXX methods. + MenuItemView(MenuItemView* parent, int command, Type type); + + private: + // Called by the two constructors to initialize this menu item. + void Init(MenuItemView* parent, + int command, + MenuItemView::Type type, + MenuDelegate* delegate); + + // All the AddXXX methods funnel into this. + MenuItemView* AppendMenuItemInternal(int item_id, + const std::wstring& label, + const SkBitmap& icon, + Type type); + + // Returns the descendant with the specified command. + MenuItemView* GetDescendantByID(int id); + + // Invoked by the MenuController when the menu closes as the result of + // drag and drop run. + void DropMenuClosed(bool notify_delegate); + + // The RunXXX methods call into this to set up the necessary state before + // running. + void PrepareForRun(bool show_mnemonics); + + // Returns the flags passed to DrawStringInt. + int GetDrawStringFlags(); + + // If this menu item has no children a child is added showing it has no + // children. Otherwise AddEmtpyMenuIfNecessary is recursively invoked on + // child menu items that have children. + void AddEmptyMenus(); + + // Undoes the work of AddEmptyMenus. + void RemoveEmptyMenus(); + + // Given bounds within our View, this helper routine mirrors the bounds if + // necessary. + void AdjustBoundsForRTLUI(RECT* rect) const; + + // Actual paint implementation. If for_drag is true, portions of the menu + // are not rendered. + void Paint(ChromeCanvas* canvas, bool for_drag); + + // Destroys the window used to display this menu and recursively destroys + // the windows used to display all descendants. + void DestroyAllMenuHosts(); + + // The delegate. This is only valid for the root menu item. You shouldn't + // use this directly, instead use GetDelegate() which walks the tree as + // as necessary. + MenuDelegate* delegate_; + + // Returns the controller for the run operation, or NULL if the menu isn't + // showing. + MenuController* controller_; + + // Used to detect when Cancel was invoked. + bool canceled_; + + // Our parent. + MenuItemView* parent_menu_item_; + + // Type of menu. NOTE: MenuItemView doesn't itself represent SEPARATOR, + // that is handled by an entirely different view class. + Type type_; + + // Whether we're selected. + bool selected_; + + // Command id. + int command_; + + // Submenu, created via CreateSubmenu. + SubmenuView* submenu_; + + // Font. + ChromeFont font_; + + // Title. + std::wstring title_; + + // Icon. + SkBitmap icon_; + + // Whether mnemonics should be shown. + bool show_mnemonics_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuItemView); +}; + +// SubmenuView ---------------------------------------------------------------- + +// SubmenuView is the parent of all menu items. +// +// SubmenuView has the following responsibilities: +// . It positions and sizes all child views (any type of View may be added, +// not just MenuItemViews). +// . Forwards the appropriate events to the MenuController. This allows the +// MenuController to update the selection as the user moves the mouse around. +// . Renders the drop indicator during a drop operation. +// . Shows and hides the window (an HWNDViewContainer) when the menu is +// shown on screen. +// +// SubmenuView is itself contained in a MenuScrollViewContainer. +// MenuScrollViewContainer handles showing as much of the SubmenuView as the +// screen allows. If the SubmenuView is taller than the screen, scroll buttons +// are provided that allow the user to see all the menu items. +class SubmenuView : public View { + public: + // Creates a SubmenuView for the specified menu item. + explicit SubmenuView(MenuItemView* parent); + ~SubmenuView(); + + // Returns the number of child views that are MenuItemViews. + // MenuItemViews are identified by ID. + int GetMenuItemCount(); + + // Returns the MenuItemView at the specified index. + MenuItemView* GetMenuItemAt(int index); + + // Positions and sizes the child views. This tiles the views vertically, + // giving each child the available width. + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + // View method. Overriden to schedule a paint. We do this so that when + // scrolling occurs, everything is repainted correctly. + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Painting. + void PaintChildren(ChromeCanvas* canvas); + + // Drag and drop methods. These are forwarded to the MenuController. + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const DropTargetEvent& event); + virtual int OnDragUpdated(const DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const DropTargetEvent& event); + + // Scrolls on menu item boundaries. + virtual bool OnMouseWheel(const MouseWheelEvent& e); + + // Returns true if the menu is showing. + bool IsShowing() const { return (host_ != NULL); } + + // Shows the menu at the specified location. Coordinates are in screen + // coordinates. max_width gives the max width the view should be. + void ShowAt(HWND parent, const gfx::Rect& bounds, bool do_capture); + + // Closes the menu. If destroy_host is true, the host is deleted. + void Close(bool destroy_host); + + // If mouse capture was grabbed, it is released. Does nothing if mouse was + // not captured. + void ReleaseCapture(); + + // Returns the parent menu item we're showing children for. + MenuItemView* GetMenuItem() const { return parent_menu_item_; } + + // Overriden to return true. This prevents tab from doing anything. + virtual bool CanProcessTabKeyEvents() { return true; } + + // Set the drop item and position. + void SetDropMenuItem(MenuItemView* item, + MenuDelegate::DropPosition position); + + // Returns whether the selection should be shown for the specified item. + // The selection is NOT shown during drag and drop when the drop is over + // the menu. + bool GetShowSelection(MenuItemView* item); + + // Returns the container for the SubmenuView. + MenuScrollViewContainer* GetScrollViewContainer(); + + // Returns the host of the menu. Returns NULL if not showing. + MenuHost* host() const { return host_; } + + private: + // Paints the drop indicator. This is only invoked if item is non-NULL and + // position is not DROP_NONE. + void PaintDropIndicator(ChromeCanvas* canvas, + MenuItemView* item, + MenuDelegate::DropPosition position); + + void SchedulePaintForDropIndicator(MenuItemView* item, + MenuDelegate::DropPosition position); + + // Calculates the location of th edrop indicator. + gfx::Rect CalculateDropIndicatorBounds(MenuItemView* item, + MenuDelegate::DropPosition position); + + // Parent menu item. + MenuItemView* parent_menu_item_; + + // HWNDViewContainer subclass used to show the children. + MenuHost* host_; + + // If non-null, indicates a drop is in progress and drop_item is the item + // the drop is over. + MenuItemView* drop_item_; + + // Position of the drop. + MenuDelegate::DropPosition drop_position_; + + // Ancestor of the SubmenuView, lazily created. + MenuScrollViewContainer* scroll_view_container_; + + DISALLOW_EVIL_CONSTRUCTORS(SubmenuView); +}; + +// MenuController ------------------------------------------------------------- + +// MenuController manages showing, selecting and drag/drop for menus. +// All relevant events are forwarded to the MenuController from SubmenuView +// and MenuHost. + +class MenuController : public MessageLoop::Dispatcher { + public: + friend class MenuHostRootView; + friend class MenuItemView; + friend class ShowSubmenusTask; + friend class MenuScrollTask; + + // If a menu is currently active, this returns the controller for it. + static MenuController* GetActiveInstance(); + + // Runs the menu at the specified location. If the menu was configured to + // block, the selected item is returned. If the menu does not block this + // returns NULL immediately. + MenuItemView* Run(HWND parent, + MenuItemView* root, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition position, + int* mouse_event_flags); + + // Whether or not Run blocks. + bool IsBlockingRun() const { return blocking_run_; } + + // Sets the selection to menu_item, a value of NULL unselects everything. + // If open_submenu is true and menu_item has a submenu, the submenu is shown. + // If update_immediately is true, submenus are opened immediately, otherwise + // submenus are only opened after a timer fires. + // + // Internally this updates pending_state_ immediatley, and if + // update_immediately is true, CommitPendingSelection is invoked to + // show/hide submenus and update state_. + void SetSelection(MenuItemView* menu_item, + bool open_submenu, + bool update_immediately); + + // Cancels the current Run. If all is true, any nested loops are canceled + // as well. This immediatley hides all menus. + void Cancel(bool all); + + // Various events, forwarded from the submenu. + // + // NOTE: the coordinates of the events are in that of the + // MenuScrollViewContainer. + void OnMousePressed(SubmenuView* source, const MouseEvent& event); + void OnMouseDragged(SubmenuView* source, const MouseEvent& event); + void OnMouseReleased(SubmenuView* source, const MouseEvent& event); + void OnMouseMoved(SubmenuView* source, const MouseEvent& event); + void OnMouseEntered(SubmenuView* source, const MouseEvent& event); + bool CanDrop(SubmenuView* source, const OSExchangeData& data); + void OnDragEntered(SubmenuView* source, const DropTargetEvent& event); + int OnDragUpdated(SubmenuView* source, const DropTargetEvent& event); + void OnDragExited(SubmenuView* source); + int OnPerformDrop(SubmenuView* source, const DropTargetEvent& event); + + // Invoked from the scroll buttons of the MenuScrollViewContainer. + void OnDragEnteredScrollButton(SubmenuView* source, bool is_up); + void OnDragExitedScrollButton(SubmenuView* source); + + private: + // As the mouse moves around submenus are not opened immediately. Instead + // they open after this timer fires. + class ShowSubmenusTask : public Task { + public: + explicit ShowSubmenusTask(MenuController* controller) + : controller_(controller) {} + + virtual void Run() { + controller_->CommitPendingSelection(); + } + + private: + MenuController* controller_; + + DISALLOW_EVIL_CONSTRUCTORS(ShowSubmenusTask); + }; + + // Task used to invoke Cancel(true). This is used during drag and drop + // to hide the menu after the mouse moves out of the of the menu. This is + // necessitated by the lack of an ability to detect when the drag has + // completed from the drop side. + class CancelAllTask : public Task { + public: + explicit CancelAllTask(MenuController* controller) + : controller_(controller) {} + + virtual void Run() { + controller_->Cancel(true); + } + + private: + MenuController* controller_; + + DISALLOW_EVIL_CONSTRUCTORS(CancelAllTask); + }; + + // Tracks selection information. + struct State { + State() : item(NULL), submenu_open(false) {} + + // The selected menu item. + MenuItemView* item; + + // If item has a submenu this indicates if the submenu is showing. + bool submenu_open; + + // Bounds passed to the run menu. Used for positioning the first menu. + gfx::Rect initial_bounds; + + // Position of the initial menu. + MenuItemView::AnchorPosition anchor; + + // The direction child menus have opened in. + std::list<bool> open_leading; + + // Bounds for the monitor we're showing on. + gfx::Rect monitor_bounds; + }; + + // Used by GetMenuPartByScreenCoordinate to indicate the menu part at a + // particular location. + struct MenuPart { + // Type of part. + enum Type { + NONE, + MENU_ITEM, + SCROLL_UP, + SCROLL_DOWN + }; + + MenuPart() : type(NONE), menu(NULL), submenu(NULL) {} + + // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP. + bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; } + + // Type of part. + Type type; + + // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise + // this is NULL. + // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item + // but is over a menu (for example, the mouse is over a separator or + // empty menu), this is NULL. + MenuItemView* menu; + + // If type is SCROLL_*, this is the submenu the mouse is over. + SubmenuView* submenu; + }; + + // Sets the active MenuController. + static void SetActiveInstance(MenuController* controller); + + // Dispatcher method. This returns true if the menu was canceled, or + // if the message is such that the menu should be closed. + virtual bool Dispatch(const MSG& msg); + + // Key processing. The return value of these is returned from Dispatch. + // In other words, if these return false (which they do if escape was + // pressed, or a matching mnemonic was found) the message loop returns. + bool OnKeyDown(const MSG& msg); + bool OnChar(const MSG& msg); + + // Creates a MenuController. If blocking is true, Run blocks the caller + explicit MenuController(bool blocking); + + ~MenuController(); + + // Invoked when the user accepts the selected item. This is only used + // when blocking. This schedules the loop to quit. + void Accept(MenuItemView* item, int mouse_event_flags); + + // Closes all menus, including any menus of nested invocations of Run. + void CloseAllNestedMenus(); + + // Gets the enabled menu item at the specified location. + // If over_any_menu is non-null it is set to indicate whether the location + // is over any menu. It is possible for this to return NULL, but + // over_any_menu to be true. For example, the user clicked on a separator. + MenuItemView* GetMenuItemAt(View* menu, int x, int y); + + // If there is an empty menu item at the specified location, it is returned. + MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y); + + // Returns true if the coordinate is over the scroll buttons of the + // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to + // indicate which scroll button the coordinate is. + bool IsScrollButtonAt(SubmenuView* source, + int x, + int y, + MenuPart::Type* part); + + // Returns the target for the mouse event. + MenuPart GetMenuPartByScreenCoordinate(SubmenuView* source, + int source_x, + int source_y); + + // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns + // true if the supplied SubmenuView contains the location in terms of the + // screen. If it does, part is set appropriately and true is returned. + bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu, + const CPoint& screen_loc, + MenuPart* part); + + // Returns true if the SubmenuView contains the specified location. This does + // NOT included the scroll buttons, only the submenu view. + bool DoesSubmenuContainLocation(SubmenuView* submenu, + const CPoint& screen_loc); + + // Opens/Closes the necessary menus such that state_ matches that of + // pending_state_. This is invoked if submenus are not opened immediately, + // but after a delay. + void CommitPendingSelection(); + + // If item has a submenu, it is closed. This does NOT update the selection + // in anyway. + void CloseMenu(MenuItemView* item); + + // If item has a submenu, it is opened. This does NOT update the selection + // in anyway. + void OpenMenu(MenuItemView* item); + + // Builds the paths of the two menu items into the two paths, and + // sets first_diff_at to the location of the first difference between the + // two paths. + void BuildPathsAndCalculateDiff(MenuItemView* old_item, + MenuItemView* new_item, + std::vector<MenuItemView*>* old_path, + std::vector<MenuItemView*>* new_path, + size_t* first_diff_at); + + // Builds the path for the specified item. + void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path); + + // Starts/stops the timer that commits the pending state to state + // (opens/closes submenus). + void StartShowTimer(); + void StopShowTimer(); + + // Starts/stops the timer cancel the menu. This is used during drag and + // drop when the drop enters/exits the menu. + void StartCancelAllTimer(); + void StopCancelAllTimer(); + + // Calculates the bounds of the menu to show. is_leading is set to match the + // direction the menu opened in. + gfx::Rect CalculateMenuBounds(MenuItemView* item, + bool prefer_leading, + bool* is_leading); + + // Returns the depth of the menu. + static int MenuDepth(MenuItemView* item); + + // Selects the next/previous menu item. + void IncrementSelection(int delta); + + // If the selected item has a submenu and it isn't currently open, the + // the selection is changed such that the menu opens immediately. + void OpenSubmenuChangeSelectionIfCan(); + + // If possible, closes the submenu. + void CloseSubmenu(); + + // Returns true if window is the window used to show item, or any of + // items ancestors. + bool IsMenuWindow(MenuItemView* item, HWND window); + + // Selects by mnemonic, and if that doesn't work tries the first character of + // the title. Returns true if a match was selected and the menu should exit. + bool SelectByChar(wchar_t key); + + // If there is a window at the location of the event, a new mouse event is + // generated and posted to it. + void RepostEvent(SubmenuView* source, const MouseEvent& event); + + // Sets the drop target to new_item. + void SetDropMenuItem(MenuItemView* new_item, + MenuDelegate::DropPosition position); + + // Starts/stops scrolling as appropriate. part gives the part the mouse is + // over. + void UpdateScrolling(const MenuPart& part); + + // Stops scrolling. + void StopScrolling(); + + // The active instance. + static MenuController* active_instance_; + + // If true, Run blocks. If false, Run doesn't block and this is used for + // drag and drop. Note that the semantics for drag and drop are slightly + // different: cancel timer is kicked off any time the drag moves outside the + // menu, mouse events do nothing... + bool blocking_run_; + + // If true, we're showing. + bool showing_; + + // If true, all nested run loops should be exited. + bool exit_all_; + + // Whether we did a capture. We do a capture only if we're blocking and + // the mouse was down when Run. + bool did_capture_; + + // As the user drags the mouse around pending_state_ changes immediately. + // When the user stops moving/dragging the mouse (or clicks the mouse) + // pending_state_ is committed to state_, potentially resulting in + // opening or closing submenus. This gives a slight delayed effect to + // submenus as the user moves the mouse around. This is done so that as the + // user moves the mouse all submenus don't immediately pop. + State pending_state_; + State state_; + + // If the user accepted the selection, this is the result. + MenuItemView* result_; + + // The mouse event flags when the user clicked on a menu. Is 0 if the + // user did not use the mousee to select the menu. + int result_mouse_event_flags_; + + // If not empty, it means we're nested. When Run is invoked from within + // Run, the current state (state_) is pushed onto menu_stack_. This allows + // MenuController to restore the state when the nested run returns. + std::list<State> menu_stack_; + + // Used to comming pending to state. + ShowSubmenusTask show_task_; + Timer* show_timer_; + + // Used to cancel all menus. + CancelAllTask cancel_all_task_; + Timer* cancel_all_timer_; + + // Drop target. + MenuItemView* drop_target_; + MenuDelegate::DropPosition drop_position_; + + // Owner of child windows. + HWND owner_; + + // Indicates a possible drag operation. + bool possible_drag_; + + // Location the mouse was pressed at. Used to detect d&d. + int press_x_; + int press_y_; + + // We get a slew of drag updated messages as the mouse is over us. To avoid + // continually processing whether we can drop, we cache the coordinates. + bool valid_drop_coordinates_; + int drop_x_; + int drop_y_; + int last_drop_operation_; + + // If true, we've invoked DoDrag on the delegate. + // + // This is used to determine whether the windows used to show menus + // are hidden or destroyed. As drag and drop is initiated during a + // mouse gesture, and the call to initiate the drag is blocking, we + // can't destroy the window immediately (otherwise when the drag and + // drop call returns the window that initiated the call and is on + // the stack has been deleted). During drag and drop (in_drag_ is + // true) windows are hidden, once the drag and drop call completes + // MenuItemView takes care of destroying the windows. + bool in_drag_; + + // If true, the mouse is over some menu. + bool any_menu_contains_mouse_; + + // If true, we're in the middle of invoking ShowAt on a submenu. + bool showing_submenu_; + + // Task for scrolling the menu. If non-null indicates a scroll is currently + // underway. + scoped_ptr<MenuScrollTask> scroll_task_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuController); +}; + +} // namespace + +#endif // CHROME_VIEWS_CHROME_MENU_H__ diff --git a/chrome/views/client_view.cc b/chrome/views/client_view.cc new file mode 100644 index 0000000..2ae6870 --- /dev/null +++ b/chrome/views/client_view.cc @@ -0,0 +1,425 @@ +// 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 <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> + +#include "chrome/views/client_view.h" + +#include "base/gfx/native_theme.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +namespace { + +// Updates any of the standard buttons according to the delegate. +void UpdateButtonHelper(ChromeViews::NativeButton* button_view, + ChromeViews::DialogDelegate* delegate, + ChromeViews::DialogDelegate::DialogButton button) { + std::wstring label = delegate->GetDialogButtonLabel(button); + if (!label.empty()) + button_view->SetLabel(label); + button_view->SetEnabled(delegate->IsDialogButtonEnabled(button)); + button_view->SetVisible(delegate->IsDialogButtonVisible(button)); +} + +} // namespace + +namespace ChromeViews { + +// static +ChromeFont ClientView::dialog_button_font_; +static const int kDialogMinButtonWidth = 75; +static const int kDialogButtonLabelSpacing = 16; +static const int kDialogButtonContentSpacing = 0; + +// The group used by the buttons. This name is chosen voluntarily big not to +// conflict with other groups that could be in the dialog content. +static const int kButtonGroup = 6666; + +namespace { + +// DialogButton ---------------------------------------------------------------- + +// DialogButtons is used for the ok/cancel buttons of the window. DialogButton +// forwrds AcceleratorPressed to the delegate. + +class DialogButton : public NativeButton { + public: + DialogButton(Window* owner, + DialogDelegate::DialogButton type, + const std::wstring& title, + bool is_default) + : NativeButton(title, is_default), owner_(owner), type_(type) { + } + + // Overriden to forward to the delegate. + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + if (!owner_->window_delegate()->AsDialogDelegate()-> + AreAcceleratorsEnabled(type_)) { + return false; + } + return NativeButton::AcceleratorPressed(accelerator); + } + + private: + Window* owner_; + const DialogDelegate::DialogButton type_; + + DISALLOW_EVIL_CONSTRUCTORS(DialogButton); +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ClientView, public: +ClientView::ClientView(Window* owner, View* contents_view) + : ok_button_(NULL), + cancel_button_(NULL), + extra_view_(NULL), + owner_(owner), + contents_view_(contents_view) { + DCHECK(owner_); + InitClass(); +} + +ClientView::~ClientView() { +} + +void ClientView::ShowDialogButtons() { + if (!owner_->window_delegate()) + return; + + DialogDelegate* dd = owner_->window_delegate()->AsDialogDelegate(); + if (!dd) + return; + + int buttons = dd->GetDialogButtons(); + + if (buttons & DialogDelegate::DIALOGBUTTON_OK && !ok_button_) { + std::wstring label = + dd->GetDialogButtonLabel(DialogDelegate::DIALOGBUTTON_OK); + if (label.empty()) + label = l10n_util::GetString(IDS_OK); + ok_button_ = new DialogButton( + owner_, DialogDelegate::DIALOGBUTTON_OK, + label, + (dd->GetDefaultDialogButton() & DialogDelegate::DIALOGBUTTON_OK) != 0); + ok_button_->SetListener(this); + ok_button_->SetGroup(kButtonGroup); + if (!cancel_button_) + ok_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + AddChildView(ok_button_); + } + if (buttons & DialogDelegate::DIALOGBUTTON_CANCEL && !cancel_button_) { + std::wstring label = + dd->GetDialogButtonLabel(DialogDelegate::DIALOGBUTTON_CANCEL); + if (label.empty()) { + if (buttons & DialogDelegate::DIALOGBUTTON_OK) { + label = l10n_util::GetString(IDS_CANCEL); + } else { + label = l10n_util::GetString(IDS_CLOSE); + } + } + cancel_button_ = new DialogButton( + owner_, DialogDelegate::DIALOGBUTTON_CANCEL, + label, + (dd->GetDefaultDialogButton() & DialogDelegate::DIALOGBUTTON_CANCEL) + != 0); + cancel_button_->SetListener(this); + cancel_button_->SetGroup(kButtonGroup); + cancel_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + AddChildView(cancel_button_); + } + + ChromeViews::View* extra_view = dd->GetExtraView(); + if (extra_view && !extra_view_) { + extra_view_ = extra_view; + extra_view_->SetGroup(kButtonGroup); + AddChildView(extra_view_); + } + if (!buttons) { + // Register the escape key as an accelerator which will close the window + // if there are no dialog buttons. + AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + } +} + +// Changing dialog labels will change button widths. +void ClientView::UpdateDialogButtons() { + if (!owner_->window_delegate()) + return; + + DialogDelegate* dd = owner_->window_delegate()->AsDialogDelegate(); + if (!dd) + return; + + int buttons = dd->GetDialogButtons(); + + if (buttons & DialogDelegate::DIALOGBUTTON_OK) + UpdateButtonHelper(ok_button_, dd, DialogDelegate::DIALOGBUTTON_OK); + + if (buttons & DialogDelegate::DIALOGBUTTON_CANCEL) + UpdateButtonHelper(cancel_button_, dd, DialogDelegate::DIALOGBUTTON_CANCEL); + + LayoutDialogButtons(); + SchedulePaint(); +} + +bool ClientView::PointIsInSizeBox(const gfx::Point& point) { + CPoint temp = point.ToPOINT(); + View::ConvertPointFromViewContainer(this, &temp); + return size_box_bounds_.Contains(temp.x, temp.y); +} + +//////////////////////////////////////////////////////////////////////////////// +// ClientView, View overrides: + +static void FillViewWithSysColor(ChromeCanvas* canvas, View* view, + COLORREF color) { + SkColor sk_color = + SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); + canvas->FillRectInt(sk_color, 0, 0, view->GetWidth(), view->GetHeight()); +} + +void ClientView::Paint(ChromeCanvas* canvas) { + if (!owner_->window_delegate()) + return; + + if (owner_->window_delegate()->AsDialogDelegate()) { + FillViewWithSysColor(canvas, this, GetSysColor(COLOR_3DFACE)); + } else { + // TODO(beng): (Cleanup) this should be COLOR_WINDOW but until the App + // Install wizard is updated to use the DialogDelegate somehow, + // we'll just use this value here. + FillViewWithSysColor(canvas, this, GetSysColor(COLOR_3DFACE)); + } +} + +void ClientView::PaintChildren(ChromeCanvas* canvas) { + View::PaintChildren(canvas); + if (!owner_->IsMaximized() && !owner_->IsMinimized()) + PaintSizeBox(canvas); +} + +void ClientView::Layout() { + if (has_dialog_buttons()) + LayoutDialogButtons(); + LayoutContentsView(); +} + +void ClientView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (is_add && child == this) { + // Only add the contents_view_ once, and only when we ourselves are added + // to the view hierarchy, since some contents_view_s assume that when they + // are added to the hierarchy a HWND exists, when it may not, since we are + // not yet added... + if (contents_view_ && contents_view_->GetParent() != this) + AddChildView(contents_view_); + // Can only add and update the dialog buttons _after_ they are added to the + // view hierarchy since they are native controls and require the + // ViewContainer's HWND. + ShowDialogButtons(); + UpdateDialogButtons(); + Layout(); + } +} + +void ClientView::DidChangeBounds(const CRect& prev, const CRect& next) { + Layout(); +} + +void ClientView::GetPreferredSize(CSize* out) { + DCHECK(out); + contents_view_->GetPreferredSize(out); + int button_height = 0; + if (has_dialog_buttons()) { + if (cancel_button_) + button_height = cancel_button_->GetHeight(); + else + button_height = ok_button_->GetHeight(); + // Account for padding above and below the button. + button_height += kDialogButtonContentSpacing + kButtonVEdgeMargin; + } + out->cy += button_height; +} + +bool ClientView::AcceleratorPressed(const Accelerator& accelerator) { + DCHECK(accelerator.GetKeyCode() == VK_ESCAPE); // We only expect Escape key. + owner_->Close(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// ClientView, NativeButton::Listener implementation: + +void ClientView::ButtonPressed(NativeButton* sender) { + if (sender == ok_button_) { + owner_->AcceptWindow(); + } else if (sender == cancel_button_) { + owner_->CancelWindow(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ClientView, private: + +void ClientView::PaintSizeBox(ChromeCanvas* canvas) { + if (!owner_->window_delegate()) + return; + + if (owner_->window_delegate()->CanResize() || + owner_->window_delegate()->CanMaximize()) { + HDC dc = canvas->beginPlatformPaint(); + SIZE gripper_size = { 0, 0 }; + gfx::NativeTheme::instance()->GetThemePartSize( + gfx::NativeTheme::STATUS, dc, SP_GRIPPER, 1, NULL, TS_TRUE, + &gripper_size); + + // TODO(beng): (http://b/1085509) In "classic" rendering mode, there isn't + // a theme-supplied gripper. We should probably improvise + // something, which would also require changing |gripper_size| + // to have different default values, too... + CRect gripper_bounds; + GetLocalBounds(&gripper_bounds, false); + gripper_bounds.left = gripper_bounds.right - gripper_size.cx; + gripper_bounds.top = gripper_bounds.bottom - gripper_size.cy; + size_box_bounds_ = gripper_bounds; + gfx::NativeTheme::instance()->PaintStatusGripper( + dc, SP_PANE, 1, 0, gripper_bounds); + canvas->endPlatformPaint(); + } +} + +int ClientView::GetButtonWidth(DialogDelegate::DialogButton button) { + if (!owner_->window_delegate()) + return kDialogMinButtonWidth; + + DialogDelegate* dd = owner_->window_delegate()->AsDialogDelegate(); + DCHECK(dd); + + std::wstring button_label = dd->GetDialogButtonLabel(button); + int string_width = dialog_button_font_.GetStringWidth(button_label); + return std::max(string_width + kDialogButtonLabelSpacing, + kDialogMinButtonWidth); +} + +void ClientView::LayoutDialogButtons() { + CRect extra_bounds; + if (cancel_button_) { + CSize ps; + cancel_button_->GetPreferredSize(&ps); + CRect lb; + GetLocalBounds(&lb, false); + int button_width = GetButtonWidth(DialogDelegate::DIALOGBUTTON_CANCEL); + CRect bounds; + bounds.left = lb.right - button_width - kButtonHEdgeMargin; + bounds.top = lb.bottom - ps.cy - kButtonVEdgeMargin; + bounds.right = bounds.left + button_width; + bounds.bottom = bounds.top + ps.cy; + cancel_button_->SetBounds(bounds); + // The extra view bounds are dependent on this button. + extra_bounds.right = bounds.left; + extra_bounds.top = bounds.top; + } + if (ok_button_) { + CSize ps; + ok_button_->GetPreferredSize(&ps); + CRect lb; + GetLocalBounds(&lb, false); + int button_width = GetButtonWidth(DialogDelegate::DIALOGBUTTON_OK); + int ok_button_right = lb.right - kButtonHEdgeMargin; + if (cancel_button_) + ok_button_right = cancel_button_->GetX() - kRelatedButtonHSpacing; + CRect bounds; + bounds.left = ok_button_right - button_width; + bounds.top = lb.bottom - ps.cy - kButtonVEdgeMargin; + bounds.right = ok_button_right; + bounds.bottom = bounds.top + ps.cy; + ok_button_->SetBounds(bounds); + // The extra view bounds are dependent on this button. + extra_bounds.right = bounds.left; + extra_bounds.top = bounds.top; + } + if (extra_view_) { + CSize ps; + extra_view_->GetPreferredSize(&ps); + CRect lb; + GetLocalBounds(&lb, false); + extra_bounds.left = lb.left + kButtonHEdgeMargin; + extra_bounds.bottom = extra_bounds.top + ps.cy; + extra_view_->SetBounds(extra_bounds); + } +} + +void ClientView::LayoutContentsView() { + // We acquire a |contents_view_| ptr when we are constructed, but this can be + // NULL (for testing purposes). Also, we explicitly don't immediately insert + // the contents_view_ into the hierarchy until we ourselves are inserted, + // because the contents_view_ may have initialization that relies on a HWND + // at the time it is inserted into _any_ hierarchy. So this check is to + // ensure the contents_view_ is in a valid state as a child of this window + // before trying to lay it out to our size. + if (contents_view_ && contents_view_->GetParent() == this) { + int button_height = 0; + if (has_dialog_buttons()) { + if (cancel_button_) + button_height = cancel_button_->GetHeight(); + else + button_height = ok_button_->GetHeight(); + // Account for padding above and below the button. + button_height += kDialogButtonContentSpacing + kButtonVEdgeMargin; + } + + CRect lb; + GetLocalBounds(&lb, false); + lb.bottom = std::max(0, static_cast<int>(lb.bottom - button_height)); + contents_view_->SetBounds(lb); + contents_view_->Layout(); + } +} + +// static +void ClientView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + dialog_button_font_ = rb.GetFont(ResourceBundle::BaseFont); + initialized = true; + } +} + +} diff --git a/chrome/views/client_view.h b/chrome/views/client_view.h new file mode 100644 index 0000000..8631c44 --- /dev/null +++ b/chrome/views/client_view.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef CHROME_VIEWS_CLIENT_VIEW_H__ +#define CHROME_VIEWS_CLIENT_VIEW_H__ + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" + +namespace ChromeViews { + +class Window; + +/////////////////////////////////////////////////////////////////////////////// +// ClientView +// +// A ClientView is a view representing the "Client" area of a Window. This is +// defined on Windows as being the portion of the window excluding the frame, +// title bar and window controls. +// +// This abstraction is used to provide both the native frame window class +// (ChromeViews::Window) and custom frame window class +// (ChromeViews::ChromeWindow) with a client view container and dialog button +// helper. ChromeWindow is used for all dialogs and windows on Windows XP and +// Windows Vista without Aero, and Window is used on Windows Vista with Aero, +// and so this class allows both Window types to share the same dialog button +// behavior. +// +/////////////////////////////////////////////////////////////////////////////// +class ClientView : public View, + public NativeButton::Listener { + public: + ClientView(Window* window, View* contents_view); + virtual ~ClientView(); + + // Adds the dialog buttons required by the supplied WindowDelegate to the + // view. + void ShowDialogButtons(); + + // Updates the enabled state and label of the buttons required by the + // supplied WindowDelegate + void UpdateDialogButtons(); + + // Returns true if the specified point (in screen coordinates) is within the + // resize box area of the window. + bool PointIsInSizeBox(const gfx::Point& point); + + // Accessors in case the user wishes to adjust these buttons. + NativeButton* ok_button() const { return ok_button_; } + NativeButton* cancel_button() const { return cancel_button_; } + + // View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void PaintChildren(ChromeCanvas* canvas); + virtual void Layout(); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual void DidChangeBounds(const CRect& prev, const CRect& next); + virtual void GetPreferredSize(CSize* out); + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // NativeButton::Listener implementation: + virtual void ButtonPressed(NativeButton* sender); + + private: + // Paint the size box in the bottom right corner of the window if it is + // resizable. + void PaintSizeBox(ChromeCanvas* canvas); + + // Returns the width of the specified dialog button using the correct font. + int GetButtonWidth(DialogDelegate::DialogButton button); + + // Position and size various sub-views. + void LayoutDialogButtons(); + void LayoutContentsView(); + + bool has_dialog_buttons() const { return ok_button_ || cancel_button_; } + + // The dialog buttons. + NativeButton* ok_button_; + NativeButton* cancel_button_; + // The button-level extra view, NULL unless the dialog delegate supplies one. + View* extra_view_; + + // The layout rect of the size box, when visible. + gfx::Rect size_box_bounds_; + + // The Window that owns us. + Window* owner_; + + // The contents of the client area, supplied by the caller. + View* contents_view_; + + // Static resource initialization + static void InitClass(); + static ChromeFont dialog_button_font_; + + DISALLOW_EVIL_CONSTRUCTORS(ClientView); +}; + +} + +#endif // #ifndef CHROME_VIEWS_CLIENT_VIEW_H__ diff --git a/chrome/views/combo_box.cc b/chrome/views/combo_box.cc new file mode 100644 index 0000000..ab5987d --- /dev/null +++ b/chrome/views/combo_box.cc @@ -0,0 +1,185 @@ +// 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 "chrome/views/combo_box.h" + +#include <windows.h> +#include "base/gfx/native_theme.h" +#include "base/gfx/rect.h" + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/resource_bundle.h" + +// Limit how small a combobox can be. +static const int kMinComboboxWidth = 148; + +// Add a couple extra pixels to the widths of comboboxes and combobox +// dropdowns so that text isn't too crowded. +static const int kComboboxExtraPaddingX = 6; + +namespace ChromeViews { +ComboBox::ComboBox(Model* model) + : model_(model), selected_item_(0), listener_(NULL), content_width_(0) { +} + +ComboBox::~ComboBox() { +} + +void ComboBox::SetListener(Listener* listener) { + listener_ = listener; +} + +void ComboBox::GetPreferredSize(CSize* out) { + HWND hwnd = GetNativeControlHWND(); + if (!hwnd) + return; + + COMBOBOXINFO cbi; + memset(reinterpret_cast<unsigned char*>(&cbi), 0, sizeof(cbi)); + cbi.cbSize = sizeof(cbi); + ::SendMessage(hwnd, CB_GETCOMBOBOXINFO, 0, reinterpret_cast<LPARAM>(&cbi)); + gfx::Rect rect_item(cbi.rcItem); + gfx::Rect rect_button(cbi.rcButton); + gfx::Size border = gfx::NativeTheme::instance()->GetThemeBorderSize( + gfx::NativeTheme::MENULIST); + + // The padding value of '3' is the xy offset from the corner of the control + // to the corner of rcItem. It does not seem to be queryable from the theme. + // It is consistent on all versions of Windows from 2K to Vista, and is + // invariant with respect to the combobox border size. We could conceivably + // get this number from rect_item.x, but it seems fragile to depend on position + // here, inside of the layout code. + const int kItemOffset = 3; + int item_to_button_distance = std::max(kItemOffset - border.width(), 0); + + // The cx computation can be read as measuring from left to right. + out->cx = std::max(kItemOffset + content_width_ + kComboboxExtraPaddingX + + item_to_button_distance + rect_button.width() + + border.width(), kMinComboboxWidth); + // The two arguments to ::max below should be typically be equal. + out->cy = std::max(rect_item.height() + 2 * kItemOffset, + rect_button.height() + 2 * border.height()); +} + +HWND ComboBox::CreateNativeControl(HWND parent_container) { + HWND r = ::CreateWindowEx(GetAdditionalExStyle(), L"COMBOBOX", L"", + WS_CHILD | WS_VSCROLL | CBS_DROPDOWNLIST, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + HFONT font = ResourceBundle::GetSharedInstance(). + GetFont(ResourceBundle::BaseFont).hfont(); + SendMessage(r, WM_SETFONT, reinterpret_cast<WPARAM>(font), FALSE); + UpdateComboBoxFromModel(r); + return r; +} + +LRESULT ComboBox::OnCommand(UINT code, int id, HWND source) { + HWND hwnd = GetNativeControlHWND(); + if (!hwnd) + return 0; + + if (code == CBN_SELCHANGE && source == hwnd) { + LRESULT r = ::SendMessage(hwnd, CB_GETCURSEL, 0, 0); + if (r != CB_ERR) { + int prev_selected_item = selected_item_; + selected_item_ = static_cast<int>(r); + if (listener_) + listener_->ItemChanged(this, prev_selected_item, selected_item_); + } + } + return 0; +} + +LRESULT ComboBox::OnNotify(int w_param, LPNMHDR l_param) { + return 0; +} + +void ComboBox::UpdateComboBoxFromModel(HWND hwnd) { + ::SendMessage(hwnd, CB_RESETCONTENT, 0, 0); + ChromeFont font = ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont); + int max_width = 0; + int num_items = model_->GetItemCount(this); + for (int i = 0; i < num_items; ++i) { + const std::wstring& text = model_->GetItemAt(this, i); + + // Inserting the Unicode formatting characters if necessary so that the + // text is displayed correctly in right-to-left UIs. + std::wstring localized_text; + const wchar_t* text_ptr = text.c_str(); + if (l10n_util::AdjustStringForLocaleDirection(text, &localized_text)) + text_ptr = localized_text.c_str(); + + ::SendMessage(hwnd, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(text_ptr)); + max_width = std::max(max_width, font.GetStringWidth(text)); + + } + content_width_ = max_width; + + if (num_items > 0) { + ::SendMessage(hwnd, CB_SETCURSEL, selected_item_, 0); + + // Set the width for the drop down while accounting for the scrollbar and + // borders. + if (num_items > ComboBox_GetMinVisible(hwnd)) + max_width += ::GetSystemMetrics(SM_CXVSCROLL); + // SM_CXEDGE would not be correct here, since the dropdown is flat, not 3D. + int kComboboxDropdownBorderSize = 1; + max_width += 2 * kComboboxDropdownBorderSize + kComboboxExtraPaddingX; + ::SendMessage(hwnd, CB_SETDROPPEDWIDTH, max_width, 0); + } +} + +void ComboBox::ModelChanged() { + HWND hwnd = GetNativeControlHWND(); + if (!hwnd) + return; + selected_item_ = std::min(0, model_->GetItemCount(this)); + UpdateComboBoxFromModel(hwnd); +} + +void ComboBox::SetSelectedItem(int index) { + selected_item_ = index; + HWND hwnd = GetNativeControlHWND(); + if (!hwnd) + return; + + // Note that we use CB_SETCURSEL and not CB_SELECTSTRING because on RTL + // locales the strings we get from our ComboBox::Model might be augmented + // with Unicode directionality marks before we insert them into the combo box + // and therefore we can not assume that the string we get from + // ComboBox::Model can be safely searched for and selected (which is what + // CB_SELECTSTRING does). + ::SendMessage(hwnd, CB_SETCURSEL, selected_item_, 0); +} + +int ComboBox::GetSelectedItem() { + return selected_item_; +} +} diff --git a/chrome/views/combo_box.h b/chrome/views/combo_box.h new file mode 100644 index 0000000..04371b7 --- /dev/null +++ b/chrome/views/combo_box.h @@ -0,0 +1,103 @@ +// 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. + +#ifndef CHROME_VIEWS_COMBO_BOX_H__ +#define CHROME_VIEWS_COMBO_BOX_H__ + +#include "chrome/views/native_control.h" + +namespace ChromeViews { +//////////////////////////////////////////////////////////////////////////////// +// +// ComboBox is a basic non editable combo box. It is initialized from a simple +// model. +// +//////////////////////////////////////////////////////////////////////////////// +class ComboBox : public NativeControl { + public: + class Model { + public: + // Return the number of items in the combo box. + virtual int GetItemCount(ComboBox* source) = 0; + + // Return the string that should be used to represent a given item. + virtual std::wstring GetItemAt(ComboBox* source, int index) = 0; + }; + + class Listener { + public: + // This is invoked once the selected item changed. + virtual void ItemChanged(ComboBox* combo_box, + int prev_index, + int new_index) = 0; + }; + + // |model is not owned by the combo box. + explicit ComboBox(Model* model); + virtual ~ComboBox(); + + // Register |listener| for item change events. + void SetListener(Listener* listener); + + // Overriden from View. + virtual void GetPreferredSize(CSize* out); + + // Overriden from NativeControl + virtual HWND CreateNativeControl(HWND parent_container); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + // Inform the combo box that its model changed. + void ModelChanged(); + + // Set / Get the selected item. + void SetSelectedItem(int index); + int GetSelectedItem(); + + private: + // Update a combo box from our model. + void UpdateComboBoxFromModel(HWND hwnd); + + // Our model. + Model* model_; + + // The current selection. + int selected_item_; + + // Item change listener. + Listener* listener_; + + // The min width, in pixels, for the text content. + int content_width_; + + DISALLOW_EVIL_CONSTRUCTORS(ComboBox); +}; +} + +#endif // CHROME_VIEWS_COMBO_BOX_H__ diff --git a/chrome/views/controller.h b/chrome/views/controller.h new file mode 100644 index 0000000..9101131 --- /dev/null +++ b/chrome/views/controller.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef CHROME_VIEWS_CONTROLLER_H_ +#define CHROME_VIEWS_CONTROLLER_H_ + +/////////////////////////////////////////////////////////////////////////////// +// +// Controller class +// +// This is the Controller portion of a MVC pattern. It handles dispatching +// commands, maintaining enabled state, and updating the UI as that state +// changes. +// +/////////////////////////////////////////////////////////////////////////////// +class Controller { + public: + // Whether or not a command is supported by this controller. + virtual bool SupportsCommand(int id) const = 0; + + // Whether or not a command is enabled. + virtual bool IsCommandEnabled(int id) const = 0; + + // Assign the provided string with a contextual label. Returns true if a + // contextual label exists and false otherwise. This method can be used when + // implementing a menu or button that needs to have a different label + // depending on the context. If this method returns false, the default + // label used when creating the button or menu is used. + virtual bool GetContextualLabel(int id, std::wstring* out) const = 0; + + // Executes a command. + virtual void ExecuteCommand(int id) = 0; +}; + +#endif // CHROME_VIEWS_CONTROLLER_H_ diff --git a/chrome/views/custom_frame_window.cc b/chrome/views/custom_frame_window.cc new file mode 100644 index 0000000..02af912 --- /dev/null +++ b/chrome/views/custom_frame_window.cc @@ -0,0 +1,1255 @@ +// 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 "chrome/views/custom_frame_window.h" + +#include "base/gfx/point.h" +#include "base/gfx/size.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/button.h" +#include "chrome/views/client_view.h" +#include "chrome/views/native_button.h" +#include "chrome/views/window_delegate.h" +#include "generated_resources.h" + +namespace ChromeViews { + +HCURSOR CustomFrameWindow::resize_cursors_[6]; + +//////////////////////////////////////////////////////////////////////////////// +// WindowResources +// +// An enumeration of bitmap resources used by this window. +enum FramePartBitmap { + FRAME_PART_BITMAP_FIRST = 0, // must be first. + + // Window Controls. + FRAME_CLOSE_BUTTON_ICON, + FRAME_CLOSE_BUTTON_ICON_H, + FRAME_CLOSE_BUTTON_ICON_P, + FRAME_CLOSE_BUTTON_ICON_SA, + FRAME_CLOSE_BUTTON_ICON_SA_H, + FRAME_CLOSE_BUTTON_ICON_SA_P, + FRAME_RESTORE_BUTTON_ICON, + FRAME_RESTORE_BUTTON_ICON_H, + FRAME_RESTORE_BUTTON_ICON_P, + FRAME_MAXIMIZE_BUTTON_ICON, + FRAME_MAXIMIZE_BUTTON_ICON_H, + FRAME_MAXIMIZE_BUTTON_ICON_P, + FRAME_MINIMIZE_BUTTON_ICON, + FRAME_MINIMIZE_BUTTON_ICON_H, + FRAME_MINIMIZE_BUTTON_ICON_P, + + // Window Frame Border. + FRAME_BOTTOM_EDGE, + FRAME_BOTTOM_LEFT_CORNER, + FRAME_BOTTOM_RIGHT_CORNER, + FRAME_LEFT_EDGE, + FRAME_RIGHT_EDGE, + FRAME_TOP_EDGE, + FRAME_TOP_LEFT_CORNER, + FRAME_TOP_RIGHT_CORNER, + + // Client Edge Border. + FRAME_CLIENT_EDGE_TOP_LEFT, + FRAME_CLIENT_EDGE_TOP, + FRAME_CLIENT_EDGE_TOP_RIGHT, + FRAME_CLIENT_EDGE_RIGHT, + FRAME_CLIENT_EDGE_BOTTOM_RIGHT, + FRAME_CLIENT_EDGE_BOTTOM, + FRAME_CLIENT_EDGE_BOTTOM_LEFT, + FRAME_CLIENT_EDGE_LEFT, + + FRAME_PART_BITMAP_COUNT // Must be last. +}; + +class WindowResources { + public: + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const = 0; + virtual const ChromeFont& GetTitleFont() const = 0; + SkColor title_color() const { return SK_ColorWHITE; } +}; + +class ActiveWindowResources : public WindowResources { + public: + ActiveWindowResources() { + InitClass(); + } + virtual ~ActiveWindowResources() { + } + + // WindowResources implementation: + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const { + return standard_frame_bitmaps_[part]; + } + virtual const ChromeFont& GetTitleFont() const { + return title_font_; + } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + static const int kFramePartBitmapIds[] = { + 0, + IDR_CLOSE, IDR_CLOSE_H, IDR_CLOSE_P, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, + IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P, + IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P, + IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P, + IDR_WINDOW_BOTTOM_CENTER, IDR_WINDOW_BOTTOM_LEFT_CORNER, + IDR_WINDOW_BOTTOM_RIGHT_CORNER, IDR_WINDOW_LEFT_SIDE, + IDR_WINDOW_RIGHT_SIDE, IDR_WINDOW_TOP_CENTER, + IDR_WINDOW_TOP_LEFT_CORNER, IDR_WINDOW_TOP_RIGHT_CORNER, + IDR_APP_TOP_LEFT, IDR_APP_TOP_CENTER, IDR_APP_TOP_RIGHT, + IDR_CONTENT_RIGHT_SIDE, IDR_CONTENT_BOTTOM_RIGHT_CORNER, + IDR_CONTENT_BOTTOM_CENTER, IDR_CONTENT_BOTTOM_LEFT_CORNER, + IDR_CONTENT_LEFT_SIDE, + 0 + }; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kFramePartBitmapIds[i]; + if (id != 0) + standard_frame_bitmaps_[i] = rb.GetBitmapNamed(id); + } + title_font_ = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1, ChromeFont::BOLD); + initialized = true; + } + } + + static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT]; + static ChromeFont title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(ActiveWindowResources); +}; + +class InactiveWindowResources : public WindowResources { + public: + InactiveWindowResources() { + InitClass(); + } + virtual ~InactiveWindowResources() { + } + + // WindowResources implementation: + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const { + return standard_frame_bitmaps_[part]; + } + virtual const ChromeFont& GetTitleFont() const { + return title_font_; + } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + static const int kFramePartBitmapIds[] = { + 0, + IDR_CLOSE, IDR_CLOSE_H, IDR_CLOSE_P, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, + IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P, + IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P, + IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P, + IDR_DEWINDOW_BOTTOM_CENTER, IDR_DEWINDOW_BOTTOM_LEFT_CORNER, + IDR_DEWINDOW_BOTTOM_RIGHT_CORNER, IDR_DEWINDOW_LEFT_SIDE, + IDR_DEWINDOW_RIGHT_SIDE, IDR_DEWINDOW_TOP_CENTER, + IDR_DEWINDOW_TOP_LEFT_CORNER, IDR_DEWINDOW_TOP_RIGHT_CORNER, + IDR_APP_TOP_LEFT, IDR_APP_TOP_CENTER, IDR_APP_TOP_RIGHT, + IDR_CONTENT_RIGHT_SIDE, IDR_CONTENT_BOTTOM_RIGHT_CORNER, + IDR_CONTENT_BOTTOM_CENTER, IDR_CONTENT_BOTTOM_LEFT_CORNER, + IDR_CONTENT_LEFT_SIDE, + 0 + }; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kFramePartBitmapIds[i]; + if (id != 0) + standard_frame_bitmaps_[i] = rb.GetBitmapNamed(id); + } + title_font_ = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1, ChromeFont::BOLD); + initialized = true; + } + } + + static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT]; + static ChromeFont title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(InactiveWindowResources); +}; + +// static +SkBitmap* ActiveWindowResources::standard_frame_bitmaps_[]; +ChromeFont ActiveWindowResources::title_font_; +SkBitmap* InactiveWindowResources::standard_frame_bitmaps_[]; +ChromeFont InactiveWindowResources::title_font_; + + +//////////////////////////////////////////////////////////////////////////////// +// +// DefaultNonClientView +// +// A ChromeView that provides the "frame" for CustomFrameWindows. This means +// rendering the non-standard window caption, border, and controls. +// +//////////////////////////////////////////////////////////////////////////////// +class DefaultNonClientView : public CustomFrameWindow::NonClientView, + public BaseButton::ButtonListener { + public: + explicit DefaultNonClientView(CustomFrameWindow* container); + virtual ~DefaultNonClientView(); + + // Overridden from CustomFrameWindow::NonClientView: + virtual void Init(ClientView* client_view); + virtual gfx::Rect CalculateClientAreaBounds(int width, int height) const; + virtual gfx::Size CalculateWindowSizeForClientSize(int width, + int height) const; + virtual CPoint GetSystemMenuPoint() const; + virtual int HitTest(const gfx::Point& point); + virtual void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask); + virtual void EnableClose(bool enable); + + // View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + // BaseButton::ButtonListener implementation: + virtual void ButtonPressed(BaseButton* sender); + + private: + // Updates the system menu icon button. + void SetWindowIcon(SkBitmap window_icon); + + // Returns the height of the non-client area at the top of the window (the + // title bar, etc). + int CalculateContentsTop() const; + + // Paint various sub-components of this view. + void PaintFrameBorder(ChromeCanvas* canvas); + void PaintMaximizedFrameBorder(ChromeCanvas* canvas); + void PaintClientEdge(ChromeCanvas* canvas); + + // Layout various sub-components of this view. + void LayoutWindowControls(); + void LayoutTitleBar(); + void LayoutClientView(); + + // Returns the resource collection to be used when rendering the window. + WindowResources* resources() const { + return container_->is_active() ? active_resources_ : inactive_resources_; + } + + // The View that provides the background for the window, and optionally + // dialog buttons. Note: the non-client view does _not_ own this view, the + // container does. + ClientView* client_view_; + + // The layout rect of the title, if visible. + gfx::Rect title_bounds_; + + // Window controls. + Button* close_button_; + Button* restore_button_; + Button* maximize_button_; + Button* minimize_button_; + Button* system_menu_button_; // Uses the window icon if visible. + bool should_show_minmax_buttons_; + + // The window icon. + SkBitmap window_icon_; + + // The window that owns this view. + CustomFrameWindow* container_; + + // Initialize various static resources. + static void InitClass(); + static WindowResources* active_resources_; + static WindowResources* inactive_resources_; + + DISALLOW_EVIL_CONSTRUCTORS(DefaultNonClientView); +}; + +// static +WindowResources* DefaultNonClientView::active_resources_ = NULL; +WindowResources* DefaultNonClientView::inactive_resources_ = NULL; +static const int kWindowControlsTopOffset = 1; +static const int kWindowControlsRightOffset = 5; +static const int kWindowControlsTopZoomedOffset = 1; +static const int kWindowControlsRightZoomedOffset = 5; +static const int kWindowTopMarginZoomed = 1; +static const int kWindowIconLeftOffset = 5; +static const int kWindowIconTopOffset = 5; +static const int kTitleTopOffset = 6; +static const int kWindowIconTitleSpacing = 3; +static const int kTitleBottomSpacing = 6; +static const int kNoTitleTopSpacing = 8; +static const int kResizeAreaSize = 5; +static const int kResizeAreaNorthSize = 3; +static const int kResizeAreaCornerSize = 16; +static const int kWindowHorizontalBorderSize = 4; +static const int kWindowVerticalBorderSize = 4; + +//////////////////////////////////////////////////////////////////////////////// +// DefaultNonClientView, public: + +DefaultNonClientView::DefaultNonClientView( + CustomFrameWindow* container) + : client_view_(NULL), + close_button_(new Button), + restore_button_(new Button), + maximize_button_(new Button), + minimize_button_(new Button), + system_menu_button_(new Button), + should_show_minmax_buttons_(false), + container_(container) { + InitClass(); + WindowResources* resources = active_resources_; + + close_button_->SetImage( + Button::BS_NORMAL, resources->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON)); + close_button_->SetImage( + Button::BS_HOT, resources->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_H)); + close_button_->SetImage( + Button::BS_PUSHED, resources->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_P)); + close_button_->SetListener(this, -1); + AddChildView(close_button_); + + restore_button_->SetImage( + Button::BS_NORMAL, resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON)); + restore_button_->SetImage( + Button::BS_HOT, resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_H)); + restore_button_->SetImage( + Button::BS_PUSHED, resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_P)); + restore_button_->SetListener(this, -1); + AddChildView(restore_button_); + + maximize_button_->SetImage( + Button::BS_NORMAL, resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON)); + maximize_button_->SetImage( + Button::BS_HOT, resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_H)); + maximize_button_->SetImage( + Button::BS_PUSHED, resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_P)); + maximize_button_->SetListener(this, -1); + AddChildView(maximize_button_); + + minimize_button_->SetImage( + Button::BS_NORMAL, resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON)); + minimize_button_->SetImage( + Button::BS_HOT, resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_H)); + minimize_button_->SetImage( + Button::BS_PUSHED, resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_P)); + minimize_button_->SetListener(this, -1); + AddChildView(minimize_button_); + + AddChildView(system_menu_button_); +} + +DefaultNonClientView::~DefaultNonClientView() { +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultNonClientView, CustomFrameWindow::NonClientView implementation: + +void DefaultNonClientView::Init(ClientView* client_view) { + client_view_ = client_view; + AddChildView(client_view_); + // TODO(beng): (Cleanup) this should mostly move down to Window, because + // it'll be needed for that version too, but with a virtual + // override here to update the NC view. + SetWindowIcon(container_->window_delegate()->GetWindowIcon()); +} + + +gfx::Rect DefaultNonClientView::CalculateClientAreaBounds( + int width, int height) const { + int top_margin = CalculateContentsTop(); + return gfx::Rect(kWindowHorizontalBorderSize, top_margin, + std::max(0, width - (2 * kWindowHorizontalBorderSize)), + std::max(0, height - top_margin - kWindowVerticalBorderSize)); +} + +gfx::Size DefaultNonClientView::CalculateWindowSizeForClientSize( + int width, int height) const { + int contents_top = CalculateContentsTop(); + return gfx::Size( + width + (2 * kWindowHorizontalBorderSize), + height + kWindowVerticalBorderSize + contents_top); +} + +CPoint DefaultNonClientView::GetSystemMenuPoint() const { + CPoint system_menu_point( + system_menu_button_->GetX(), + system_menu_button_->GetY() + system_menu_button_->GetHeight()); + MapWindowPoints(container_->GetHWND(), HWND_DESKTOP, &system_menu_point, 1); + return system_menu_point; +} + +// There is a subtle point that needs to be explained regarding the manner in +// which this function returns the HT* code Windows is expecting: +// +// |point| contains the cursor position in this View's coordinate system. If +// this View uses a right-to-left UI layout, the position represented by +// |point| will not reflect the UI mirroring because we don't create the +// container's HWND with WS_EX_LAYOUTRTL. Therefore, whenever the cursor +// position resides within the boundaries of one of our child Views (for +// example, the close_button_), we must retrieve the child View bounds such +// that bound are mirrored if the View uses right-to-left UI layout. This is +// why this function passes APPLY_MIRRORING_TRANSFORMATION as the |settings| +// whenever it calls GetBounds(). +int DefaultNonClientView::HitTest(const gfx::Point& point) { + CRect bounds; + CPoint test_point = point.ToPOINT(); + + // First see if it's within the grow box area, since that overlaps the client + // bounds. + if (client_view_->PointIsInSizeBox(point)) + return HTBOTTOMRIGHT; + + // Then see if it's within the client area. + if (client_view_) { + client_view_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTCLIENT; + } + + // Then see if the point is within any of the window controls. + close_button_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTCLOSE; + restore_button_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTMAXBUTTON; + maximize_button_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTMAXBUTTON; + minimize_button_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTMINBUTTON; + system_menu_button_->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTSYSMENU; + + // Then see if the point is within the resize boundaries. + int width = GetWidth(); + int height = GetHeight(); + int component = HTNOWHERE; + if (point.x() < kResizeAreaSize) { + if (point.y() < kResizeAreaCornerSize) { + component = HTTOPLEFT; + } else if (point.y() >= (height - kResizeAreaCornerSize)) { + component = HTBOTTOMLEFT; + } else { + component = HTLEFT; + } + } else if (point.x() < kResizeAreaCornerSize) { + if (point.y() < kResizeAreaNorthSize) { + component = HTTOPLEFT; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOMLEFT; + } + } else if (point.x() >= (width - kResizeAreaSize)) { + if (point.y() < kResizeAreaCornerSize) { + component = HTTOPRIGHT; + } else if (point.y() >= (height - kResizeAreaCornerSize)) { + component = HTBOTTOMRIGHT; + } else if (point.x() >= (width - kResizeAreaSize)) { + component = HTRIGHT; + } + } else if (point.x() >= (width - kResizeAreaCornerSize)) { + if (point.y() < kResizeAreaNorthSize) { + component = HTTOPRIGHT; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOMRIGHT; + } + } else if (point.y() < kResizeAreaNorthSize) { + component = HTTOP; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOM; + } + + // If the window can't be resized, there are no resize boundaries, just + // window borders. + if (component != HTNOWHERE) { + if (container_->window_delegate() && + !container_->window_delegate()->CanResize()) { + return HTBORDER; + } + return component; + } + + // Finally fall back to the caption. + GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(test_point)) + return HTCAPTION; + // The point is outside the window's bounds. + return HTNOWHERE; +} + +void DefaultNonClientView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + DCHECK(window_mask); + + // Redefine the window visible region for the new size. + window_mask->moveTo(0, 3); + window_mask->lineTo(1, 1); + window_mask->lineTo(3, 0); + + window_mask->lineTo(SkIntToScalar(size.width() - 3), 0); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 1); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 3); + window_mask->lineTo(SkIntToScalar(size.width()), 3); + + window_mask->lineTo(SkIntToScalar(size.width()), + SkIntToScalar(size.height())); + window_mask->lineTo(0, SkIntToScalar(size.height())); + window_mask->close(); +} + +void DefaultNonClientView::EnableClose(bool enable) { + close_button_->SetEnabled(enable); +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultNonClientView, View overrides: + +void DefaultNonClientView::Paint(ChromeCanvas* canvas) { + if (container_->IsMaximized()) { + PaintMaximizedFrameBorder(canvas); + } else { + PaintFrameBorder(canvas); + } + PaintClientEdge(canvas); + + WindowDelegate* d = container_->window_delegate(); + if (d->ShouldShowWindowTitle()) { + canvas->DrawStringInt(d->GetWindowTitle(), + resources()->GetTitleFont(), + resources()->title_color(), title_bounds_.x(), + title_bounds_.y(), title_bounds_.width(), + title_bounds_.height()); + } +} + +void DefaultNonClientView::Layout() { + LayoutWindowControls(); + LayoutTitleBar(); + LayoutClientView(); +} + +void DefaultNonClientView::GetPreferredSize(CSize* out) { + DCHECK(out); + if (client_view_) { + client_view_->GetPreferredSize(out); + out->cx += 2 * kWindowHorizontalBorderSize; + out->cy += CalculateContentsTop() + kWindowVerticalBorderSize; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultNonClientView, BaseButton::ButtonListener implementation: + +void DefaultNonClientView::ButtonPressed(BaseButton* sender) { + if (sender == close_button_) { + container_->ExecuteSystemMenuCommand(SC_CLOSE); + } else if (sender == minimize_button_) { + container_->ExecuteSystemMenuCommand(SC_MINIMIZE); + } else if (sender == maximize_button_) { + container_->ExecuteSystemMenuCommand(SC_MAXIMIZE); + } else if (sender == restore_button_) { + container_->ExecuteSystemMenuCommand(SC_RESTORE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultNonClientView, private: + +void DefaultNonClientView::SetWindowIcon(SkBitmap window_icon) { + // TODO(beng): (Cleanup) remove this persistent cache of the icon when Button + // takes a SkBitmap rather than SkBitmap*. + window_icon_ = window_icon; + system_menu_button_->SetImage(Button::BS_NORMAL, &window_icon); +} + +int DefaultNonClientView::CalculateContentsTop() const { + if (container_->window_delegate()->ShouldShowWindowTitle()) { + return kTitleTopOffset + resources()->GetTitleFont().height() + + kTitleBottomSpacing; + } + return kNoTitleTopSpacing; +} + +void DefaultNonClientView::PaintFrameBorder(ChromeCanvas* canvas) { + int width = GetWidth(); + int height = GetHeight(); + + SkBitmap* top_left_corner = + resources()->GetPartBitmap(FRAME_TOP_LEFT_CORNER); + SkBitmap* top_right_corner = + resources()->GetPartBitmap(FRAME_TOP_RIGHT_CORNER); + SkBitmap* top_edge = resources()->GetPartBitmap(FRAME_TOP_EDGE); + SkBitmap* right_edge = resources()->GetPartBitmap(FRAME_RIGHT_EDGE); + SkBitmap* left_edge = resources()->GetPartBitmap(FRAME_LEFT_EDGE); + SkBitmap* bottom_left_corner = + resources()->GetPartBitmap(FRAME_BOTTOM_LEFT_CORNER); + SkBitmap* bottom_right_corner = + resources()->GetPartBitmap(FRAME_BOTTOM_RIGHT_CORNER); + SkBitmap* bottom_edge = resources()->GetPartBitmap(FRAME_BOTTOM_EDGE); + + // Top. + canvas->DrawBitmapInt(*top_left_corner, 0, 0); + canvas->TileImageInt(*top_edge, top_left_corner->width(), 0, + width - top_right_corner->width(), top_edge->height()); + canvas->DrawBitmapInt(*top_right_corner, + width - top_right_corner->width(), 0); + + // Right. + int top_stack_height = top_right_corner->height(); + canvas->TileImageInt(*right_edge, width - right_edge->width(), + top_stack_height, right_edge->width(), + height - top_stack_height - + bottom_right_corner->height()); + + // Bottom. + canvas->DrawBitmapInt(*bottom_right_corner, + width - bottom_right_corner->width(), + height - bottom_right_corner->height()); + canvas->TileImageInt(*bottom_edge, bottom_left_corner->width(), + height - bottom_edge->height(), + width - bottom_left_corner->width() - + bottom_right_corner->width(), + bottom_edge->height()); + canvas->DrawBitmapInt(*bottom_left_corner, 0, + height - bottom_left_corner->height()); + + // Left. + top_stack_height = top_left_corner->height(); + canvas->TileImageInt(*left_edge, 0, top_stack_height, left_edge->width(), + height - top_stack_height - + bottom_left_corner->height()); +} + +void DefaultNonClientView::PaintMaximizedFrameBorder( + ChromeCanvas* canvas) { + SkBitmap* top_edge = resources()->GetPartBitmap(FRAME_TOP_EDGE); + SkBitmap* bottom_edge = + resources()->GetPartBitmap(FRAME_BOTTOM_EDGE); + canvas->TileImageInt(*top_edge, 0, 0, GetWidth(), top_edge->height()); + canvas->TileImageInt(*bottom_edge, 0, GetHeight() - bottom_edge->height(), + GetWidth(), bottom_edge->height()); +} + +void DefaultNonClientView::PaintClientEdge(ChromeCanvas* canvas) { + SkBitmap* top_left = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP_LEFT); + SkBitmap* top = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP); + SkBitmap* top_right = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP_RIGHT); + SkBitmap* right = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_RIGHT); + SkBitmap* bottom_right = + resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM_RIGHT); + SkBitmap* bottom = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM); + SkBitmap* bottom_left = + resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM_LEFT); + SkBitmap* left = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_LEFT); + + CRect client_area_bounds; + client_view_->GetBounds(&client_area_bounds); + + canvas->DrawBitmapInt(*top_left, client_area_bounds.left - top_left->width(), + client_area_bounds.top - top->height()); + canvas->TileImageInt(*top, client_area_bounds.left, + client_area_bounds.top - top->height(), + client_area_bounds.Width(), top->height()); + canvas->DrawBitmapInt(*top_right, client_area_bounds.right, + client_area_bounds.top - top->height()); + canvas->TileImageInt(*right, client_area_bounds.right, + client_area_bounds.top - top->height() + + top_right->height(), + right->width(), client_area_bounds.Height()); + canvas->DrawBitmapInt(*bottom_right, client_area_bounds.right, + client_area_bounds.bottom); + canvas->TileImageInt(*bottom, client_area_bounds.left, + client_area_bounds.bottom, + client_area_bounds.Width(), bottom_right->height()); + canvas->DrawBitmapInt(*bottom_left, + client_area_bounds.left - bottom_left->width(), + client_area_bounds.bottom); + canvas->TileImageInt(*left, client_area_bounds.left - left->width(), + client_area_bounds.top - top->height() + + top_left->height(), + left->width(), client_area_bounds.Height()); +} + +void DefaultNonClientView::LayoutWindowControls() { + CSize ps; + if (container_->IsMaximized() || container_->IsMinimized()) { + maximize_button_->SetVisible(false); + restore_button_->SetVisible(true); + } + + if (container_->IsMaximized()) { + close_button_->GetPreferredSize(&ps); + close_button_->SetImageAlignment(Button::ALIGN_LEFT, Button::ALIGN_BOTTOM); + close_button_->SetBounds( + GetWidth() - ps.cx - kWindowControlsRightZoomedOffset, + 0, ps.cx + kWindowControlsRightZoomedOffset, + ps.cy + kWindowControlsTopZoomedOffset); + + if (should_show_minmax_buttons_) { + restore_button_->GetPreferredSize(&ps); + restore_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_BOTTOM); + restore_button_->SetBounds(close_button_->GetX() - ps.cx, 0, ps.cx, + ps.cy + kWindowControlsTopZoomedOffset); + + minimize_button_->GetPreferredSize(&ps); + minimize_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_BOTTOM); + minimize_button_->SetBounds(restore_button_->GetX() - ps.cx, 0, ps.cx, + ps.cy + kWindowControlsTopZoomedOffset); + } + } else if (container_->IsMinimized()) { + close_button_->GetPreferredSize(&ps); + close_button_->SetImageAlignment(Button::ALIGN_LEFT, Button::ALIGN_BOTTOM); + close_button_->SetBounds( + GetWidth() - ps.cx - kWindowControlsRightZoomedOffset, + 0, ps.cx + kWindowControlsRightZoomedOffset, + ps.cy + kWindowControlsTopZoomedOffset); + + if (should_show_minmax_buttons_) { + restore_button_->GetPreferredSize(&ps); + restore_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_BOTTOM); + restore_button_->SetBounds(close_button_->GetX() - ps.cx, 0, ps.cx, + ps.cy + kWindowControlsTopZoomedOffset); + + minimize_button_->GetPreferredSize(&ps); + minimize_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_BOTTOM); + minimize_button_->SetBounds(restore_button_->GetX() - ps.cx, 0, ps.cx, + ps.cy + kWindowControlsTopZoomedOffset); + } + } else { + close_button_->GetPreferredSize(&ps); + close_button_->SetImageAlignment(Button::ALIGN_LEFT, Button::ALIGN_TOP); + close_button_->SetBounds(GetWidth() - kWindowControlsRightOffset - ps.cx, + kWindowControlsTopOffset, ps.cx, ps.cy); + + if (should_show_minmax_buttons_) { + close_button_->SetImage( + Button::BS_NORMAL, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON)); + close_button_->SetImage( + Button::BS_HOT, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_H)); + close_button_->SetImage( + Button::BS_PUSHED, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_P)); + + restore_button_->SetVisible(false); + + maximize_button_->SetVisible(true); + maximize_button_->GetPreferredSize(&ps); + maximize_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_TOP); + maximize_button_->SetBounds(close_button_->GetX() - ps.cx, + kWindowControlsTopOffset, ps.cx, ps.cy); + + minimize_button_->GetPreferredSize(&ps); + minimize_button_->SetImageAlignment(Button::ALIGN_LEFT, + Button::ALIGN_TOP); + minimize_button_->SetBounds(maximize_button_->GetX() - ps.cx, + kWindowControlsTopOffset, ps.cx, ps.cy); + } + } + if (!should_show_minmax_buttons_) { + close_button_->SetImage( + Button::BS_NORMAL, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_SA)); + close_button_->SetImage( + Button::BS_HOT, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_SA_H)); + close_button_->SetImage( + Button::BS_PUSHED, + active_resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_SA_P)); + + restore_button_->SetVisible(false); + maximize_button_->SetVisible(false); + minimize_button_->SetVisible(false); + } +} + +void DefaultNonClientView::LayoutTitleBar() { + int top_offset = container_->IsMaximized() ? kWindowTopMarginZoomed : 0; + WindowDelegate* d = container_->window_delegate(); + + // Size the window icon, if visible. + if (d->ShouldShowWindowIcon()) { + system_menu_button_->SetVisible(true); + CSize ps; + system_menu_button_->GetPreferredSize(&ps); + system_menu_button_->SetBounds( + kWindowIconLeftOffset, kWindowIconTopOffset + top_offset, ps.cx, ps.cy); + } else { + // Put the menu in the right place at least even if it is hidden so we + // can size the title based on its position. + system_menu_button_->SetBounds(kWindowIconLeftOffset, + kWindowIconTopOffset, 0, 0); + } + + // Size the title, if visible. + if (d->ShouldShowWindowTitle()) { + CRect system_menu_bounds; + system_menu_button_->GetBounds(&system_menu_bounds); + int spacing = d->ShouldShowWindowIcon() ? kWindowIconTitleSpacing : 0; + int title_right = should_show_minmax_buttons_ ? + minimize_button_->GetX() : close_button_->GetX(); + int title_left = system_menu_bounds.right + spacing; + title_bounds_.SetRect(title_left, kTitleTopOffset + top_offset, + std::max(0, static_cast<int>(title_right - system_menu_bounds.right)), + resources()->GetTitleFont().height()); + + // We draw the custom frame window's title directly rather than using a + // ChromeViews::Label child view. Therefore, we have to mirror the title + // position manually if the View's UI layout is right-to-left. Child Views + // are automatically mirrored, which means that the parent view doesn't + // need to manually modify their position depending on the View's UI + // layout. + // + // Mirroring the title's position manually is certainly far from being + // elegant, but we have no choice (other than changing the + // DefaultNonClientView subclass to use a ChromeView::Label as a child View + // instead of drawing the title's text directly on the canvas). + title_bounds_.set_x(MirroredLeftPointForRect(title_bounds_)); + } +} + +void DefaultNonClientView::LayoutClientView() { + if (client_view_) { + gfx::Rect client_bounds( + CalculateClientAreaBounds(GetWidth(), GetHeight())); + client_view_->SetBounds(client_bounds.ToRECT()); + } +} + +// static +void DefaultNonClientView::InitClass() { + static bool initialized = false; + if (!initialized) { + active_resources_ = new ActiveWindowResources; + inactive_resources_ = new InactiveWindowResources; + initialized = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomFrameWindow, public: + +CustomFrameWindow::CustomFrameWindow() + : Window(), + non_client_view_(new DefaultNonClientView(this)), + is_active_(false) { + InitClass(); +} + +CustomFrameWindow::CustomFrameWindow(NonClientView* non_client_view) + : Window(), + non_client_view_(non_client_view) { + InitClass(); +} + +CustomFrameWindow::~CustomFrameWindow() { +} + +void CustomFrameWindow::Init(HWND owner, const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate) { + Window::Init(owner, bounds, contents_view, window_delegate); + // We need to re-parent the client view to the non-client view. + GetRootView()->RemoveChildView(client_view_); + GetRootView()->AddChildView(non_client_view_); + non_client_view_->Init(client_view_); + GetRootView()->Layout(); + + ResetWindowRegion(); +} + +void CustomFrameWindow::ExecuteSystemMenuCommand(int command) { + if (command) + SendMessage(GetHWND(), WM_SYSCOMMAND, command, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomFrameWindow, Window overrides: + +gfx::Size CustomFrameWindow::CalculateWindowSizeForClientSize( + const gfx::Size& client_size) const { + return non_client_view_->CalculateWindowSizeForClientSize( + client_size.width(), client_size.height()); +} + +void CustomFrameWindow::UpdateWindowTitle() { + // Layout winds up causing the title to be re-validated during + // string measurement. + non_client_view_->Layout(); +} + +void CustomFrameWindow::EnableClose(bool enable) { + non_client_view_->EnableClose(enable); + // Make sure the SysMenu changes to reflect this change as well. + Window::EnableClose(enable); +} + +void CustomFrameWindow::SizeWindowToDefault() { + CSize pref(0, 0); + client_view_->GetPreferredSize(&pref); + DCHECK(pref.cx > 0 && pref.cy > 0); + gfx::Size window_size = + non_client_view_->CalculateWindowSizeForClientSize(pref.cx, pref.cy); + win_util::CenterAndSizeWindow(owning_window(), GetHWND(), + window_size.ToSIZE(), false); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomFrameWindow, HWNDViewContainer overrides: + +static void EnableMenuItem(HMENU menu, UINT command, bool enabled) { + UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED); + EnableMenuItem(menu, command, flags); +} + +void CustomFrameWindow::OnInitMenu(HMENU menu) { + bool minimized = IsMinimized(); + bool maximized = IsMaximized(); + bool minimized_or_maximized = minimized || maximized; + + EnableMenuItem(menu, SC_RESTORE, + window_delegate()->CanMaximize() && minimized_or_maximized); + EnableMenuItem(menu, SC_MOVE, !minimized_or_maximized); + EnableMenuItem(menu, SC_SIZE, + window_delegate()->CanResize() && !minimized_or_maximized); + EnableMenuItem(menu, SC_MAXIMIZE, + window_delegate()->CanMaximize() && !maximized); + EnableMenuItem(menu, SC_MINIMIZE, + window_delegate()->CanMaximize() && !minimized); +} + +void CustomFrameWindow::OnMouseLeave() { + bool process_mouse_exited = true; + POINT pt; + if (GetCursorPos(&pt)) { + LRESULT ht_component = + ::SendMessage(GetHWND(), WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)); + if (ht_component != HTNOWHERE) { + // If the mouse moved into a part of the window's non-client area, then + // don't send a mouse exited event since the mouse is still within the + // bounds of the ChromeView that's rendering the frame. Note that we do + // _NOT_ do this for windows with native frames, since in that case the + // mouse really will have left the bounds of the RootView. + process_mouse_exited = false; + } + } + + if (process_mouse_exited) + ProcessMouseExited(); +} + +LRESULT CustomFrameWindow::OnNCActivate(BOOL active) { + is_active_ = !!active; + + // We can get WM_NCACTIVATE before we're actually visible. If we're not + // visible, no need to paint. + if (!IsMaximized() && IsWindowVisible(GetHWND())) { + non_client_view_->SchedulePaint(); + // We need to force a paint now, as a user dragging a window will block + // painting operations while the move is in progress. + PaintNow(root_view_->GetScheduledPaintRect()); + } + return TRUE; +} + +LRESULT CustomFrameWindow::OnNCCalcSize(BOOL mode, LPARAM l_param) { + CRect client_bounds; + if (!mode) { + RECT* rect = reinterpret_cast<RECT*>(l_param); + *rect = non_client_view_->CalculateClientAreaBounds( + rect->right - rect->left, rect->bottom - rect->top).ToRECT(); + } + return 0; +} + +LRESULT CustomFrameWindow::OnNCHitTest(const CPoint& point) { + // NC points are in screen coordinates. + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); + return non_client_view_->HitTest(gfx::Point(temp.x, temp.y)); +} + +LRESULT CustomFrameWindow::OnNCMouseMove(UINT flags, const CPoint& point) { + // NC points are in screen coordinates. + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); + ProcessMouseMoved(temp, 0); + + // We need to process this message to stop Windows from drawing the window + // controls as the mouse moves over the title bar area when the window is + // maximized. + return 0; +} + +struct ClipState { + // The window being painted. + HWND parent; + + // DC painting to. + HDC dc; + + // Origin of the window in terms of the screen. + int x; + int y; +}; + +// See comments in OnNCPaint for details of this function. +static BOOL CALLBACK ClipDCToChild(HWND window, LPARAM param) { + ClipState* clip_state = reinterpret_cast<ClipState*>(param); + if (GetParent(window) == clip_state->parent && IsWindowVisible(window)) { + RECT bounds; + GetWindowRect(window, &bounds); + ExcludeClipRect(clip_state->dc, + bounds.left - clip_state->x, + bounds.top - clip_state->y, + bounds.right - clip_state->x, + bounds.bottom - clip_state->y); + } + return TRUE; +} + +void CustomFrameWindow::OnNCPaint(HRGN rgn) { + // We have an NC region and need to paint it. We expand the NC region to + // include the dirty region of the root view. This is done to minimize + // paints. + CRect window_rect; + GetWindowRect(&window_rect); + + CRect dirty_region; + // A value of 1 indicates paint all. + if (!rgn || rgn == reinterpret_cast<HRGN>(1)) { + dirty_region = CRect(0, 0, window_rect.Width(), window_rect.Height()); + } else { + RECT rgn_bounding_box; + GetRgnBox(rgn, &rgn_bounding_box); + if (!IntersectRect(&dirty_region, &rgn_bounding_box, &window_rect)) + return; // Dirty region doesn't intersect window bounds, bale. + + // rgn_bounding_box is in screen coordinates. Map it to window coordinates. + OffsetRect(&dirty_region, -window_rect.left, -window_rect.top); + } + + // In theory GetDCEx should do what we want, but I couldn't get it to work. + // In particular the docs mentiond DCX_CLIPCHILDREN, but as far as I can tell + // it doesn't work at all. So, instead we get the DC for the window then + // manually clip out the children. + HDC dc = GetWindowDC(GetHWND()); + ClipState clip_state; + clip_state.x = window_rect.left; + clip_state.y = window_rect.top; + clip_state.parent = GetHWND(); + clip_state.dc = dc; + EnumChildWindows(GetHWND(), &ClipDCToChild, + reinterpret_cast<LPARAM>(&clip_state)); + + RootView* root_view = GetRootView(); + CRect old_paint_region = root_view->GetScheduledPaintRectConstrainedToSize(); + + if (!old_paint_region.IsRectEmpty()) { + // The root view has a region that needs to be painted. Include it in the + // region we're going to paint. + + CRect tmp = dirty_region; + UnionRect(&dirty_region, &tmp, &old_paint_region); + } + + root_view->SchedulePaint(dirty_region, false); + + // ChromeCanvasPaints destructor does the actual painting. As such, wrap the + // following in a block to force paint to occur so that we can release the dc. + { + ChromeCanvasPaint canvas(dc, opaque(), dirty_region.left, dirty_region.top, + dirty_region.Width(), dirty_region.Height()); + + root_view->ProcessPaint(&canvas); + } + + ReleaseDC(GetHWND(), dc); +} + +void CustomFrameWindow::OnNCRButtonDown(UINT flags, const CPoint& point) { + if (flags == HTCAPTION || flags == HTSYSMENU) { + RunSystemMenu(point); + } else { + SetMsgHandled(FALSE); + } +} + +void CustomFrameWindow::OnNCLButtonDown(UINT ht_component, + const CPoint& point) { + switch (ht_component) { + case HTCLOSE: + case HTMINBUTTON: + case HTMAXBUTTON: { + // When the mouse is pressed down in these specific non-client areas, we + // need to tell the RootView to send the mouse pressed event (which sets + // capture, allowing subsequent WM_LBUTTONUP (note, _not_ WM_NCLBUTTONUP) + // to fire so that the appropriate WM_SYSCOMMAND can be sent by the + // applicable button's ButtonListener. We _have_ to do this this way + // rather than letting Windows just send the syscommand itself (as would + // happen if we never did this dance) because for some insane reason + // DefWindowProc for WM_NCLBUTTONDOWN also renders the pressed window + // control button appearance, in the Windows classic style, over our + // view! Ick! By handling this message we prevent Windows from doing this + // undesirable thing, but that means we need to roll the sys-command + // handling ourselves. + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); + UINT flags = 0; + if ((GetKeyState(VK_CONTROL) & 0x80) == 0x80) + flags |= MK_CONTROL; + if ((GetKeyState(VK_SHIFT) & 0x80) == 0x80) + flags |= MK_SHIFT; + flags |= MK_LBUTTON; + ProcessMousePressed(temp, flags, false); + SetMsgHandled(TRUE); + return; + } + case HTSYSMENU: + RunSystemMenu(non_client_view_->GetSystemMenuPoint()); + break; + } + SetMsgHandled(FALSE); +} + +LRESULT CustomFrameWindow::OnSetCursor(HWND window, UINT hittest_code, + UINT message) { + int index = RC_NORMAL; + switch (hittest_code) { + case HTTOP: + case HTBOTTOM: + index = RC_VERTICAL; + break; + case HTTOPLEFT: + case HTBOTTOMRIGHT: + index = RC_NWSE; + break; + case HTTOPRIGHT: + case HTBOTTOMLEFT: + index = RC_NESW; + break; + case HTLEFT: + case HTRIGHT: + index = RC_HORIZONTAL; + break; + case HTCAPTION: + case HTCLIENT: + index = RC_NORMAL; + break; + } + SetCursor(resize_cursors_[index]); + return 0; +} + +void CustomFrameWindow::OnSize(UINT param, const CSize& size) { + Window::OnSize(param, size); + + // ResetWindowRegion is going to trigger WM_NCPAINT. By doing it after we've + // invoked OnSize we ensure the RootView has been layed out. + ResetWindowRegion(); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomFrameWindow, private: + +void CustomFrameWindow::RunSystemMenu(const CPoint& point) { + HMENU system_menu = ::GetSystemMenu(GetHWND(), FALSE); + int id = ::TrackPopupMenu(system_menu, + TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, + point.x, point.y, 0, GetHWND(), NULL); + ExecuteSystemMenuCommand(id); +} + +// static +void CustomFrameWindow::InitClass() { + static bool initialized = false; + if (!initialized) { + resize_cursors_[RC_NORMAL] = LoadCursor(NULL, IDC_ARROW); + resize_cursors_[RC_VERTICAL] = LoadCursor(NULL, IDC_SIZENS); + resize_cursors_[RC_HORIZONTAL] = LoadCursor(NULL, IDC_SIZEWE); + resize_cursors_[RC_NESW] = LoadCursor(NULL, IDC_SIZENESW); + resize_cursors_[RC_NWSE] = LoadCursor(NULL, IDC_SIZENWSE); + initialized = true; + } +} + +void CustomFrameWindow::ResetWindowRegion() { + // Changing the window region is going to force a paint. Only change the + // window region if the region really differs. + HRGN current_rgn = CreateRectRgn(0, 0, 0, 0); + int current_rgn_result = GetWindowRgn(GetHWND(), current_rgn); + + HRGN new_region = NULL; + if (!IsMaximized()) { + CRect window_rect; + GetWindowRect(&window_rect); + gfx::Path window_mask; + non_client_view_->GetWindowMask(gfx::Size(window_rect.Width(), + window_rect.Height()), + &window_mask); + new_region = window_mask.CreateHRGN(); + } + + if (current_rgn_result == ERROR || + !EqualRgn(current_rgn, new_region)) { + // SetWindowRgn takes ownership of the HRGN created by CreateHRGN. + SetWindowRgn(new_region, TRUE); + } else if (new_region) { + DeleteObject(new_region); + } + + DeleteObject(current_rgn); +} + +} // namespace ChromeViews diff --git a/chrome/views/custom_frame_window.h b/chrome/views/custom_frame_window.h new file mode 100644 index 0000000..8dab9d9 --- /dev/null +++ b/chrome/views/custom_frame_window.h @@ -0,0 +1,158 @@ +// 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. + +#ifndef CHROME_VIEWS_CUSTOM_FRAME_WINDOW_H__ +#define CHROME_VIEWS_CUSTOM_FRAME_WINDOW_H__ + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/window.h" +#include "chrome/views/window_delegate.h" + +namespace ChromeViews { + +//////////////////////////////////////////////////////////////////////////////// +// +// CustomFrameWindow +// +// A CustomFrameWindow is a Window subclass that implements the Chrome-style +// window frame used on Windows XP and Vista without DWM Composition. +// See documentation in window.h for more information about the capabilities +// of this window type. +// +//////////////////////////////////////////////////////////////////////////////// +class CustomFrameWindow : public Window { + public: + CustomFrameWindow(); + class NonClientView; + explicit CustomFrameWindow(NonClientView* non_client_view); + virtual ~CustomFrameWindow(); + + // Create the CustomFrameWindow. + // The parent of this window is always the desktop, however the owner is a + // window that this window is dependent on, if this window is opened as a + // modal dialog or dependent window. This is NULL if the window is not + // dependent on any other window. + // |contents_view| is the view to be displayed in the client area of the + // window. + // |window_delegate| is an object implementing the WindowDelegate interface + // that supplies information to the window such as its title, icon, etc. + virtual void Init(HWND owner, + const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate); + + // Executes the specified SC_command. + void ExecuteSystemMenuCommand(int command); + + // Returns whether or not the frame is active. + bool is_active() const { return is_active_; } + + class NonClientView : public View { + public: + virtual void Init(ClientView* client_view) = 0; + + // Calculates the bounds of the client area of the window assuming the + // window is sized to |width| and |height|. + virtual gfx::Rect CalculateClientAreaBounds(int width, + int height) const = 0; + + // Calculates the size of window required to display a client area of the + // specified width and height. + virtual gfx::Size CalculateWindowSizeForClientSize(int width, + int height) const = 0; + + // Returns the point, in screen coordinates, where the system menu should + // be shown so it shows up anchored to the system menu icon. + virtual CPoint GetSystemMenuPoint() const = 0; + + // Determines the windows HT* code when the mouse cursor is at the + // specified point, in window coordinates. + virtual int HitTest(const gfx::Point& point) = 0; + + // Returns a mask to be used to clip the top level window for the given + // size. This is used to create the non-rectangular window shape. + virtual void GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) = 0; + + // Toggles the enable state for the Close button (and the Close menu item in + // the system menu). + virtual void EnableClose(bool enable) = 0; + }; + + // Overridden from Window: + virtual gfx::Size CalculateWindowSizeForClientSize( + const gfx::Size& client_size) const; + virtual void UpdateWindowTitle(); + + protected: + // Overridden from Window: + virtual void SizeWindowToDefault(); + virtual void EnableClose(bool enable); + + // Overridden from HWNDViewContainer: + virtual void OnInitMenu(HMENU menu); + virtual void OnMouseLeave(); + virtual LRESULT OnNCActivate(BOOL active); + virtual LRESULT OnNCCalcSize(BOOL mode, LPARAM l_param); + virtual LRESULT OnNCHitTest(const CPoint& point); + virtual LRESULT OnNCMouseMove(UINT flags, const CPoint& point); + virtual void OnNCPaint(HRGN rgn); + virtual void OnNCRButtonDown(UINT flags, const CPoint& point); + virtual void OnNCLButtonDown(UINT flags, const CPoint& point); + virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message); + virtual void OnSize(UINT param, const CSize& size); + + // The View that provides the non-client area of the window (title bar, + // window controls, sizing borders etc). + NonClientView* non_client_view_; + + private: + + // Shows the system menu at the specified screen point. + void RunSystemMenu(const CPoint& point); + + // Resets the window region. + void ResetWindowRegion(); + + // True if this window is the active top level window. + bool is_active_; + + // Static resource initialization. + static void InitClass(); + enum ResizeCursor { + RC_NORMAL = 0, RC_VERTICAL, RC_HORIZONTAL, RC_NESW, RC_NWSE + }; + static HCURSOR resize_cursors_[6]; + + DISALLOW_EVIL_CONSTRUCTORS(CustomFrameWindow); +}; + +} + +#endif // CHROME_VIEWS_CUSTOM_FRAME_WINDOW_H__ diff --git a/chrome/views/decision.cc b/chrome/views/decision.cc new file mode 100644 index 0000000..658c8c9 --- /dev/null +++ b/chrome/views/decision.cc @@ -0,0 +1,186 @@ +// 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 <string> + +#include "chrome/views/decision.h" + +#include "chrome/common/resource_bundle.h" +#include "chrome/views/label.h" +#include "chrome/views/grid_layout.h" + +using namespace std; + +namespace ChromeViews { + +static const int kPaddingEdge = 10; +static const int kSpacingInfoBottom = 20; + +class Option : public View, + public NativeButton::Listener { + public: + Option(int command_id, + const std::wstring& description, + const std::wstring& action, + Controller* controller); + + // NativeButton::Listener methods: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + private: + int command_id_; + Controller* controller_; +}; + +Decision::Decision(const std::wstring& title, + const std::wstring& details, + Controller* controller) + : controller_(controller) { + // The main message. + title_label_ = new Label(title); + title_label_->SetFont( + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::LargeFont)); + title_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(title_label_); + + // The detailed description. + details_label_ = new Label(details); + details_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + details_label_->SetMultiLine(true); + AddChildView(details_label_); +} + +void Decision::AppendOption(int command_id, + const std::wstring& description, + const std::wstring& action) { + Option* option = new Option(command_id, description, action, controller_); + options_.push_back(option); + AddChildView(option); +} + +void Decision::DidChangeBounds(const CRect& old_bounds, + const CRect& new_bounds) { + Layout(); +} + +void Decision::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (is_add && child == this) { + // Layout when this is added so that the buttons are laid out correctly. + Layout(); + } +} + +void Decision::Layout() { + CRect lb; + GetLocalBounds(&lb, false); + + // Resize for padding. + lb.DeflateRect(kPaddingEdge, kPaddingEdge); + int width = lb.Width(); + + CPoint position(lb.TopLeft()); + CSize size; + title_label_->GetPreferredSize(&size); + title_label_->SetBounds(position.x, position.y, width, size.cy); + position.y += size.cy + kSpacingInfoBottom; + + size.cy = details_label_->GetHeightForWidth(width); + details_label_->SetBounds(position.x, position.y, width, size.cy); + position.y += size.cy + kSpacingInfoBottom; + + for (std::vector<Option*>::const_iterator iter = options_.begin(); + iter != options_.end(); ++iter) { + Option* option = *iter; + option->GetPreferredSize(&size); + option->SetBounds(position.x, position.y, width, size.cy); + option->Layout(); + position.y += size.cy + kSpacingInfoBottom; + } +} + +void Decision::GetPreferredSize(CSize *out) { + int width = 0; + int height = 0; + + // We need to find the largest width from the title and the options, as the + // details label is multi-line and we need to known its width in order to + // compute its height. + CSize size; + title_label_->GetPreferredSize(&size); + width = size.cx; + height = size.cy + kSpacingInfoBottom; + + for (std::vector<Option*>::const_iterator iter = options_.begin(); + iter != options_.end(); ++iter) { + (*iter)->GetPreferredSize(&size); + if (size.cx > width) + width = size.cx; + height += size.cy + kSpacingInfoBottom; + } + + // Now we can compute the details label height. + height += details_label_->GetHeightForWidth(width) + kSpacingInfoBottom; + + out->cx = width + 2 * kPaddingEdge; + out->cy = height + 2 * kPaddingEdge; +} + +Option::Option(int command_id, + const std::wstring& description, + const std::wstring& action, + Controller* controller) + : command_id_(command_id), + controller_(controller) { + + GridLayout* layout = new GridLayout(this); + SetLayoutManager(layout); + + ColumnSet* columns = layout->AddColumnSet(0); + columns->AddColumn(GridLayout::FILL, GridLayout::CENTER, + 1, GridLayout::USE_PREF, 0, 0); + columns->AddPaddingColumn(0, 10); + columns->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, + 0, GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + Label* label = new Label(description); + label->SetHorizontalAlignment(Label::ALIGN_LEFT); + layout->AddView(label); + + // A button to perform the action. + NativeButton* button = new NativeButton(action); + button->SetListener(this); + layout->AddView(button); +} + +void Option::ButtonPressed(ChromeViews::NativeButton* sender) { + controller_->ExecuteCommand(command_id_); +} + +} // namespace ChromeViews diff --git a/chrome/views/decision.h b/chrome/views/decision.h new file mode 100644 index 0000000..8795457 --- /dev/null +++ b/chrome/views/decision.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef CHROME_VIEWS_DECISION_H__ +#define CHROME_VIEWS_DECISION_H__ + +#include <string> + +#include "chrome/views/controller.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class Label; +class Option; + +// A view that presents a user with a decision. This view contains a title and +// a general description. Users of this class should append at least one option +// for the user to select. +class Decision : public View { + public: + // The |title| appears in large font at the top of the view. The |details| + // appear in a multi-line text area below the title. The |controller| is + // notified when the user selects an option. + Decision(const std::wstring& title, + const std::wstring& details, + Controller* controller); + + // Append an option to the view. The |description| explains this option to + // the user. The |action| text is the text the user will click on to select + // this option. If the user selects this option, the controller will be asked + // to ExecuteCommand |command_id|. + void AppendOption(int command_id, + const std::wstring& description, + const std::wstring& action); + + // Need to override this to call layout. + virtual void DidChangeBounds(const CRect& old_bounds, + const CRect& new_bounds); + + // Overridden from View for custom layout. + virtual void Layout(); + virtual void GetPreferredSize(CSize *out); + + protected: + // Override to call Layout(). + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + private: + // Our controller. + Controller* controller_; + + // The views. + Label* title_label_; + Label* details_label_; + + // The option views that have been added. + std::vector<Option*> options_; +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_DECISION_H__ diff --git a/chrome/views/dialog_delegate.h b/chrome/views/dialog_delegate.h new file mode 100644 index 0000000..2dc48e6 --- /dev/null +++ b/chrome/views/dialog_delegate.h @@ -0,0 +1,126 @@ +// 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. + +#ifndef CHROME_VIEWS_DIALOG_DELEGATE_H__ +#define CHROME_VIEWS_DIALOG_DELEGATE_H__ + +#include "chrome/views/window_delegate.h" + +namespace ChromeViews { + +class NativeButton; +class View; + +/////////////////////////////////////////////////////////////////////////////// +// +// DialogDelegate +// +// DialogDelegate is an interface implemented by objects that wish to show a +// dialog box Window. The window that is displayed uses this interface to +// determine how it should be displayed and notify the delegate object of +// certain events. +// +/////////////////////////////////////////////////////////////////////////////// +class DialogDelegate : public WindowDelegate { + public: + virtual DialogDelegate* AsDialogDelegate() { return this; } + + enum DialogButton { + DIALOGBUTTON_NONE = 0, // No dialog buttons, for WindowType == WINDOW. + DIALOGBUTTON_OK = 1, // Has an OK button. + DIALOGBUTTON_CANCEL = 2, // Has a Cancel button (becomes a Close button if + }; // no OK button). + + // Returns a mask specifying which of the available DialogButtons are visible + // for the dialog. Note: If an OK button is provided, you should provide a + // CANCEL button. A dialog box with just an OK button is frowned upon and + // considered a very special case, so if you're planning on including one, + // you should reconsider, or beng says there will be stabbings. + // + // To use the extra button you need to override GetDialogButtons() + virtual int GetDialogButtons() const { + return DIALOGBUTTON_OK | DIALOGBUTTON_CANCEL; + } + + // Returns whether accelerators are enabled on the button. This is invoked + // when an accelerator is pressed, not at construction time. This + // returns true. + virtual bool AreAcceleratorsEnabled(DialogButton button) { return true; } + + // Returns the label of the specified DialogButton. + virtual std::wstring GetDialogButtonLabel(DialogButton button) const { + // empty string results in defaults for DIALOGBUTTON_OK, + // DIALOGBUTTON_CANCEL. + return L""; + } + + // Override this function if with a view which will be shown in the same + // row as the OK and CANCEL buttons but flush to the left and extending + // up to the buttons. + virtual ChromeViews::View* GetExtraView() { return NULL; } + + // Returns the default dialog button. This should not be a mask as only one + // button should ever be the default button. Return DIALOGBUTTON_NONE if + // there is no default. + virtual int GetDefaultDialogButton() const { + return DIALOGBUTTON_OK; + } + + // Returns whether the specified dialog button is enabled. + virtual bool IsDialogButtonEnabled(DialogButton button) const { + return true; + } + + // Returns whether the specified dialog button is visible. + virtual bool IsDialogButtonVisible(DialogButton button) const { + return true; + } + + // For Dialog boxes, if there is a "Cancel" button, this is called when the + // user presses the "Cancel" button or the Close button on the window or + // in the system menu, or presses the Esc key. This function should return + // true if the window can be closed after it returns, or false if it must + // remain open. + virtual bool Cancel() { return true; } + + // For Dialog boxes, this is called when the user presses the "OK" button, + // or the Enter key. Can also be called on Esc key or close button presses if + // there is no "Cancel" button. This function should return true if the window + // can be closed after the window can be closed after it returns, or false if + // it must remain open. If |window_closing| is true, it means that this + // handler is being called because the window is being closed (e.g. by + // Window::Close) and there is no Cancel handler, so Accept is being called + // instead. + virtual bool Accept(bool window_closing) { return Accept(); } + virtual bool Accept() { return true; } +}; + +} // namespace ChromeViews + +#endif // #ifndef CHROME_VIEWS_DIALOG_DELEGATE_H__ diff --git a/chrome/views/event.cc b/chrome/views/event.cc new file mode 100644 index 0000000..b3d9194 --- /dev/null +++ b/chrome/views/event.cc @@ -0,0 +1,112 @@ +// 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 "chrome/views/event.h" + +#include "chrome/views/view.h" +#include "webkit/glue/webinputevent.h" + +namespace ChromeViews { + +int Event::GetWindowsFlags() const { + // TODO: need support for x1/x2. + int result = 0; + result |= (flags_ & EF_SHIFT_DOWN) ? MK_SHIFT : 0; + result |= (flags_ & EF_CONTROL_DOWN) ? MK_CONTROL : 0; + result |= (flags_ & EF_LEFT_BUTTON_DOWN) ? MK_LBUTTON : 0; + result |= (flags_ & EF_MIDDLE_BUTTON_DOWN) ? MK_MBUTTON : 0; + result |= (flags_ & EF_RIGHT_BUTTON_DOWN) ? MK_RBUTTON : 0; + return result; +} + +//static +int Event::ConvertWindowsFlags(UINT win_flags) { + int r = 0; + if (win_flags & MK_CONTROL) + r |= EF_CONTROL_DOWN; + if (win_flags & MK_SHIFT) + r |= EF_SHIFT_DOWN; + if (GetKeyState(VK_MENU) < 0) + r |= EF_ALT_DOWN; + if (win_flags & MK_LBUTTON) + r |= EF_LEFT_BUTTON_DOWN; + if (win_flags & MK_MBUTTON) + r |= EF_MIDDLE_BUTTON_DOWN; + if (win_flags & MK_RBUTTON) + r |= EF_RIGHT_BUTTON_DOWN; + return r; +} + +// static +int Event::ConvertWebInputEventFlags(int web_input_event_flags) { + int r = 0; + if (web_input_event_flags & WebInputEvent::SHIFT_KEY) + r |= EF_SHIFT_DOWN; + if (web_input_event_flags & WebInputEvent::CTRL_KEY) + r |= EF_CONTROL_DOWN; + if (web_input_event_flags & WebInputEvent::ALT_KEY) + r |= EF_ALT_DOWN; + return r; +} + +LocatedEvent::LocatedEvent(const LocatedEvent& model, View* from, View* to) + : Event(model), + location_(model.location_) { + if (to) + View::ConvertPointToView(from, to, &location_); +} + +MouseEvent::MouseEvent(EventType type, + View* from, + View* to, + const gfx::Point &l, + int flags) + : LocatedEvent(LocatedEvent(type, gfx::Point(l.x(), l.y()), flags), + from, + to) { +}; + +MouseEvent::MouseEvent(const MouseEvent& model, View* from, View* to) + : LocatedEvent(model, from, to) { +} + +int KeyEvent::GetKeyStateFlags() const { + // Windows Keyboard messages don't come with control key state as parameters + // like mouse messages do, so we need to explicitly probe for these key states. + int flags = 0; + if (GetKeyState(VK_MENU) & 0x80) + flags |= Event::EF_ALT_DOWN; + if (GetKeyState(VK_SHIFT) & 0x80) + flags |= Event::EF_SHIFT_DOWN; + if (GetKeyState(VK_CONTROL) & 0x80) + flags |= Event::EF_CONTROL_DOWN; + return flags; +} + +} diff --git a/chrome/views/event.h b/chrome/views/event.h new file mode 100644 index 0000000..a9b93bf --- /dev/null +++ b/chrome/views/event.h @@ -0,0 +1,351 @@ +// 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. + +#ifndef CHROME_VIEWS_EVENT_H__ +#define CHROME_VIEWS_EVENT_H__ + +// TODO(maruel): Remove these as soon as LocatedEvent::GetLocation() is +// removed. +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> + +#include "base/basictypes.h" +#include "base/gfx/point.h" +#include "webkit/glue/window_open_disposition.h" + +class OSExchangeData; + +namespace ChromeViews { + +class View; + +//////////////////////////////////////////////////////////////////////////////// +// +// Event class +// +// An event encapsulates an input event that can be propagated into view +// hierarchies. An event has a type, some flags and a time stamp. +// +// Each major event type has a corresponding Event subclass. +// +// Events are immutable but support copy +// +//////////////////////////////////////////////////////////////////////////////// +class Event { + public: + // Event types. (prefixed because of a conflict with windows headers) + enum EventType { ET_UNKNOWN = 0, + ET_MOUSE_PRESSED, + ET_MOUSE_DRAGGED, + ET_MOUSE_RELEASED, + ET_MOUSE_MOVED, + ET_MOUSE_ENTERED, + ET_MOUSE_EXITED, + ET_KEY_PRESSED, + ET_KEY_RELEASED, + ET_MOUSEWHEEL, + ET_DROP_TARGET_EVENT }; + + // Event flags currently supported + enum EventFlags { EF_SHIFT_DOWN = 1 << 0, + EF_CONTROL_DOWN = 1 << 1, + EF_ALT_DOWN = 1 << 2, + EF_LEFT_BUTTON_DOWN = 1 << 3, + EF_MIDDLE_BUTTON_DOWN = 1 << 4, + EF_RIGHT_BUTTON_DOWN = 1 << 5 }; + + // Return the event type + EventType GetType() const { + return type_; + } + + // Return the event time stamp in ticks + int GetTimeStamp() const { + return time_stamp_; + } + + // Return the flags + int GetFlags() const { + return flags_; + } + + // Return whether the shift modifier is down + bool IsShiftDown() const { + return (flags_ & EF_SHIFT_DOWN) != 0; + } + + // Return whether the control modifier is down + bool IsControlDown() const { + return (flags_ & EF_CONTROL_DOWN) != 0; + } + + // Return whether the alt modifier is down + bool IsAltDown() const { + return (flags_ & EF_ALT_DOWN) != 0; + } + + // Returns the EventFlags in terms of windows flags. + int GetWindowsFlags() const; + + // Convert windows flags to ChromeViews::Event flags + static int ConvertWindowsFlags(UINT win_flags); + + // Convert WebInputEvent::Modifiers flags to ChromeViews::Event flags. + // Note that this only deals with keyboard modifiers. + static int ConvertWebInputEventFlags(int web_input_event_flags); + + protected: + Event(EventType type, int flags) + : type_(type), + time_stamp_(GetTickCount()), + flags_(flags) { + } + + Event(const Event& model) + : type_(model.GetType()), + time_stamp_(model.GetTimeStamp()), + flags_(model.GetFlags()) { + } + + private: + void operator=(const Event&); + + EventType type_; + int time_stamp_; + int flags_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// LocatedEvent class +// +// A generifc event that is used for any events that is located at a specific +// position in the screen. +// +//////////////////////////////////////////////////////////////////////////////// +class LocatedEvent : public Event { + public: + LocatedEvent(EventType type, const gfx::Point& location, int flags) + : Event(type, flags), + location_(location) { + } + + // Create a new LocatedEvent which is identical to the provided model. + // If from / to views are provided, the model location will be converted + // from 'from' coordinate system to 'to' coordinate system + LocatedEvent(const LocatedEvent& model, View* from, View* to); + + // Returns the X location. + int GetX() const { + return location_.x(); + } + + // Returns the Y location. + int GetY() const { + return location_.y(); + } + + // Returns the location. + const gfx::Point& location() const { + return location_; + } + + // WARNING: DEPRECATED. Returns the location in WTL::CPoint format. + WTL::CPoint GetLocation() const { + return WTL::CPoint(location_.x(), location_.y()); + } + + private: + gfx::Point location_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// MouseEvent class +// +// A mouse event is used for any input event related to the mouse. +// +//////////////////////////////////////////////////////////////////////////////// +class MouseEvent : public LocatedEvent { + public: + // Flags specific to mouse events + enum MouseEventFlags { EF_IS_DOUBLE_CLICK = 1 << 16 }; + + // Create a new mouse event + MouseEvent(EventType type, int x, int y, int flags) + : LocatedEvent(type, gfx::Point(x, y), flags) { + } + + // Create a new mouse event from a type and a point. If from / to views + // are provided, the point will be converted from 'from' coordinate system to + // 'to' coordinate system. + MouseEvent(EventType type, + View* from, + View* to, + const gfx::Point &l, + int flags); + + // Create a new MouseEvent which is identical to the provided model. + // If from / to views are provided, the model location will be converted + // from 'from' coordinate system to 'to' coordinate system + MouseEvent(const MouseEvent& model, View* from, View* to); + + // Conveniences to quickly test what button is down + bool IsOnlyLeftMouseButton() const { + return (GetFlags() & EF_LEFT_BUTTON_DOWN) && + !(GetFlags() & (EF_MIDDLE_BUTTON_DOWN | EF_RIGHT_BUTTON_DOWN)); + } + + bool IsLeftMouseButton() const { + return (GetFlags() & EF_LEFT_BUTTON_DOWN) != 0; + } + + bool IsOnlyMiddleMouseButton() const { + return (GetFlags() & EF_MIDDLE_BUTTON_DOWN) && + !(GetFlags() & (EF_LEFT_BUTTON_DOWN | EF_RIGHT_BUTTON_DOWN)); + } + + bool IsMiddleMouseButton() const { + return (GetFlags() & EF_MIDDLE_BUTTON_DOWN) != 0; + } + + bool IsOnlyRightMouseButton() const { + return (GetFlags() & EF_RIGHT_BUTTON_DOWN) && + !(GetFlags() & (EF_LEFT_BUTTON_DOWN | EF_MIDDLE_BUTTON_DOWN)); + } + + bool IsRightMouseButton() const { + return (GetFlags() & EF_RIGHT_BUTTON_DOWN) != 0; + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(MouseEvent); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// KeyEvent class +// +// A key event is used for any input event related to the keyboard. +// Note: this event is about key pressed, not typed characters. +// +//////////////////////////////////////////////////////////////////////////////// +class KeyEvent : public Event { + public: + // Create a new key event + KeyEvent(EventType type, int ch, int repeat_count, int message_flags) + : Event(type, GetKeyStateFlags()), + character_(ch), + repeat_count_(repeat_count), + message_flags_(message_flags) { + } + + int GetCharacter() const { + return character_; + } + + bool IsExtendedKey() const { + return (message_flags_ & KF_EXTENDED) == KF_EXTENDED; + } + + int GetRepeatCount() const { + return repeat_count_; + } + + private: + int GetKeyStateFlags() const; + + int character_; + int repeat_count_; + int message_flags_; + + DISALLOW_EVIL_CONSTRUCTORS(KeyEvent); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// MouseWheelEvent class +// +// A MouseWheelEvent is used to propagate mouse wheel user events +// +//////////////////////////////////////////////////////////////////////////////// +class MouseWheelEvent : public LocatedEvent { + public: + // Create a new key event + MouseWheelEvent(int offset, int x, int y, int flags) + : LocatedEvent(ET_MOUSEWHEEL, gfx::Point(x, y), flags), + offset_(offset) { + } + + int GetOffset() const { + return offset_; + } + + private: + int offset_; + + DISALLOW_EVIL_CONSTRUCTORS(MouseWheelEvent); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// DropTargetEvent class +// +// A DropTargetEvent is sent to the view the mouse is over during a drag and +// drop operation. +// +//////////////////////////////////////////////////////////////////////////////// +class DropTargetEvent : public LocatedEvent { + public: + DropTargetEvent(const OSExchangeData& data, + int x, + int y, + int source_operations) + : LocatedEvent(ET_DROP_TARGET_EVENT, gfx::Point(x, y), 0), + data_(data), + source_operations_(source_operations) { + } + + // Data associated with the drag/drop session. + const OSExchangeData& GetData() const { return data_; } + + // Bitmask of supported DragDropTypes::DragOperation by the source. + int GetSourceOperations() const { return source_operations_; } + + private: + const OSExchangeData& data_; + int source_operations_; + + DISALLOW_EVIL_CONSTRUCTORS(DropTargetEvent); +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_EVENT_H__ diff --git a/chrome/views/external_focus_tracker.cc b/chrome/views/external_focus_tracker.cc new file mode 100644 index 0000000..0e0ffa6 --- /dev/null +++ b/chrome/views/external_focus_tracker.cc @@ -0,0 +1,91 @@ +// 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 "chrome/views/external_focus_tracker.h" + +#include "chrome/views/view.h" +#include "chrome/views/view_storage.h" + +namespace ChromeViews { + +ExternalFocusTracker::ExternalFocusTracker( + ChromeViews::View* parent_view, ChromeViews::FocusManager* focus_manager) + : focus_manager_(focus_manager), + parent_view_(parent_view) { + view_storage_ = ChromeViews::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( + ChromeViews::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 diff --git a/chrome/views/external_focus_tracker.h b/chrome/views/external_focus_tracker.h new file mode 100644 index 0000000..5e25a2a --- /dev/null +++ b/chrome/views/external_focus_tracker.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef CHROME_BROWSER_VIEWS_EXTERNAL_FOCUS_TRACKER_H__ +#define CHROME_BROWSER_VIEWS_EXTERNAL_FOCUS_TRACKER_H__ + +#include "chrome/views/focus_manager.h" + +namespace ChromeViews { + +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(ChromeViews::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 +#endif // CHROME_BROWSER_VIEWS_EXTERNAL_FOCUS_TRACKER_H__ 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); +} + +} diff --git a/chrome/views/focus_manager.h b/chrome/views/focus_manager.h new file mode 100644 index 0000000..0537e5c --- /dev/null +++ b/chrome/views/focus_manager.h @@ -0,0 +1,354 @@ +// 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. + +#ifndef CHROME_VIEWS_FOCUS_MANAGER_H__ +#define CHROME_VIEWS_FOCUS_MANAGER_H__ + +#include <windows.h> +#include <vector> +#include <map> + +#include "base/basictypes.h" +#include "chrome/common/notification_service.h" +#include "chrome/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 ChromeViews::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 HWNDViewContainer, 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 HWNDViewContainer 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 HWNDViewContainer 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 ChromeViews { + +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. + virtual bool ProcessKeyDown(HWND window, UINT message, WPARAM wparam, + LPARAM lparam) = 0; +}; + +// 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 NotificationObserver { + public: + // 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); + // OnPostActivate is called after WM_ACTIVATE has been propagated to the + // DefWindowProc. + bool OnPostActivate(HWND window, int activation_state, int minimized_state); + + // 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(); + + // Focus the specified |hwnd| without changing the focused view. + void FocusHWND(HWND hwnd); + + // Validates the focused view, clearing it if the window it belongs too is not + // attached to the window hierarchy anymore. + void ValidateFocusedView(); + + // 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); + + // 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 all keyboard accelerator for the specified target. + void UnregisterAccelerators(AcceleratorTarget* target); + + // Activate the target associated with the specified accelerator if any. + // If |prioritary_accelerators_only| is true, only the following accelerators + // are allowed: + // - 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) + // Returns true if an accelerator was activated. + bool ProcessAccelerator(const Accelerator& accelerator, + bool prioritary_accelerators_only); + + // NotificationObserver method. + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + 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); + + private: + explicit FocusManager(HWND root, RootView* root_view); + ~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); + + // Returns the AcceleratorTarget that should be activated for the specified + // keyboard accelerator, or NULL if no view is registered for that keyboard + // accelerator. + AcceleratorTarget* GetTargetForAccelerator( + const Accelerator& accelerator) const; + + // 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_; + + // The window associated with this focus manager. + HWND root_; + + // 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_EVIL_CONSTRUCTORS(FocusManager); +}; + +} + +#endif // CHROME_VIEWS_FOCUS_MANAGER_H__ diff --git a/chrome/views/focus_manager_unittest.cc b/chrome/views/focus_manager_unittest.cc new file mode 100644 index 0000000..a7a8379 --- /dev/null +++ b/chrome/views/focus_manager_unittest.cc @@ -0,0 +1,688 @@ +// 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. + +// 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 "base/gfx/rect.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/accelerator_handler.h" +#include "chrome/views/background.h" +#include "chrome/views/border.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/hwnd_view_container.h" +#include "chrome/views/label.h" +#include "chrome/views/link.h" +#include "chrome/views/native_button.h" +#include "chrome/views/radio_button.h" +#include "chrome/views/scroll_view.h" +#include "chrome/views/tabbed_pane.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" +#include "chrome/views/window_delegate.h" +#include "SkColor.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 ChromeViews::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, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + // Create the view container which is a child of the TabControl. + view_container_ = new ChromeViews::HWNDViewContainer(); + view_container_->Init(tab_control, gfx::Rect(), child_, false); + view_container_->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 ChromeViews::RootView* GetContentsRootView() { + return view_container_->GetRootView(); + } + + virtual ChromeViews::FocusTraversable* GetFocusTraversable() { + return view_container_; + } + + 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. + view_container_->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); + view_container_->MoveWindow(content_bounds.left, content_bounds.top, + content_bounds.Width(), content_bounds.Height(), + TRUE); + } + + View* child_; + ChromeViews::HWNDViewContainer* view_container_; + + DISALLOW_EVIL_CONSTRUCTORS(BorderView); +}; + +class TestViewWindow : public ChromeViews::Window, + public ChromeViews::WindowDelegate { + public: + explicit TestViewWindow(FocusManagerTest* test); + ~TestViewWindow() { } + + void Init(); + virtual std::wstring GetWindowTitle() const; + + // 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); + + ChromeViews::RootView* GetContentsRootView() const { + return contents_->GetRootView(); + } + + ChromeViews::RootView* GetStyleRootView() const { + return style_tab_->GetContentsRootView(); + } + + ChromeViews::RootView* GetSearchRootView() const { + return search_border_view_->GetContentsRootView(); + } + + private: + ChromeViews::View* contents_; + + ChromeViews::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(); + + 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 ChromeViews::View(); + contents_->SetBackground( + ChromeViews::Background::CreateSolidBackground(255, 255, 255)); + + Window::Init(NULL, bounds, contents_, this); + + ChromeViews::CheckBox* cb = + new ChromeViews::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); + + ChromeViews::View* left_container = new ChromeViews::View(); + left_container->SetBorder( + ChromeViews::Border::CreateSolidBorder(1, SK_ColorBLACK)); + left_container->SetBackground( + ChromeViews::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; + + ChromeViews::Label* label = new ChromeViews::Label(L"Apple:"); + label->SetID(kAppleLabelID); + left_container->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + ChromeViews::TextField* text_field = new ChromeViews::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 ChromeViews::Label(L"Orange:"); + label->SetID(kOrangeLabelID); + left_container->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new ChromeViews::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 ChromeViews::Label(L"Banana:"); + label->SetID(kBananaLabelID); + left_container->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new ChromeViews::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 ChromeViews::Label(L"Kiwi:"); + label->SetID(kKiwiLabelID); + left_container->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new ChromeViews::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; + + ChromeViews::NativeButton* button = + new ChromeViews::NativeButton(L"Click me"); + button->SetBounds(label_x, y + 10, 50, 20); + button->SetID(kFruitButtonID); + left_container->AddChildView(button); + y += 40; + + cb = new ChromeViews::CheckBox(L"This is another check box"); + cb->SetBounds(label_x + label_width + 5, y, 100, 20); + cb->SetID(kFruitCheckBoxID); + left_container->AddChildView(cb); + + ChromeViews::View* right_container = new ChromeViews::View(); + right_container->SetBorder( + ChromeViews::Border::CreateSolidBorder(1, SK_ColorBLACK)); + right_container->SetBackground( + ChromeViews::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; + ChromeViews::View* radio_button = + new ChromeViews::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 ChromeViews::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 ChromeViews::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; + + ChromeViews::View* inner_container = new ChromeViews::View(); + inner_container->SetBorder( + ChromeViews::Border::CreateSolidBorder(1, SK_ColorBLACK)); + inner_container->SetBackground( + ChromeViews::Background::CreateSolidBackground(230, 230, 230)); + inner_container->SetID(kInnerContainerID); + right_container->AddChildView(inner_container); + inner_container->SetBounds(100, 10, 150, 180); + + ChromeViews::ScrollView* scroll_view = new ChromeViews::ScrollView(); + scroll_view->SetID(kScrollViewID); + inner_container->AddChildView(scroll_view); + scroll_view->SetBounds(1, 1, 148, 178); + + ChromeViews::View* scroll_content = new ChromeViews::View(); + scroll_content->SetBounds(0, 0, 200, 200); + scroll_content->SetBackground( + ChromeViews::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) { + ChromeViews::Link* link = new ChromeViews::Link(kTitles[i]); + link->SetHorizontalAlignment(ChromeViews::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 ChromeViews::NativeButton(L"OK"); + button->SetID(kOKButtonID); + + contents_->AddChildView(button); + button->SetBounds(150, y, width, 20); + + button = new ChromeViews::NativeButton(L"Cancel"); + button->SetID(kCancelButtonID); + contents_->AddChildView(button); + button->SetBounds(250, y, width, 20); + + button = new ChromeViews::NativeButton(L"Help"); + button->SetID(kHelpButtonID); + contents_->AddChildView(button); + button->SetBounds(350, y, width, 20); + + y += 40; + + // Left bottom box with style checkboxes. + ChromeViews::View* contents = new ChromeViews::View(); + contents->SetBackground( + ChromeViews::Background::CreateSolidBackground(SK_ColorWHITE)); + cb = new ChromeViews::CheckBox(L"Bold"); + contents->AddChildView(cb); + cb->SetBounds(10, 10, 50, 20); + cb->SetID(kBoldCheckBoxID); + + cb = new ChromeViews::CheckBox(L"Italic"); + contents->AddChildView(cb); + cb->SetBounds(70, 10, 50, 20); + cb->SetID(kItalicCheckBoxID); + + cb = new ChromeViews::CheckBox(L"Underlined"); + contents->AddChildView(cb); + cb->SetBounds(130, 10, 70, 20); + cb->SetID(kUnderlinedCheckBoxID); + + style_tab_ = new ChromeViews::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 ChromeViews::View()); + + // Right bottom box with search. + contents = new ChromeViews::View(); + contents->SetBackground( + ChromeViews::Background::CreateSolidBackground(SK_ColorWHITE)); + text_field = new ChromeViews::TextField(); + contents->AddChildView(text_field); + text_field->SetBounds(10, 10, 100, 20); + text_field->SetID(kSearchTextFieldID); + + button = new ChromeViews::NativeButton(L"Search"); + contents->AddChildView(button); + button->SetBounds(115, 10, 50, 20); + button->SetID(kSearchButtonID); + + ChromeViews::Link* link = new ChromeViews::Link(L"Help"); + link->SetHorizontalAlignment(ChromeViews::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 ChromeViews::View(); + contents->SetFocusable(true); + contents->SetBackground( + ChromeViews::Background::CreateSolidBackground(SK_ColorBLUE)); + contents->SetID(kThumbnailContainerID); + button = new ChromeViews::NativeButton(L"Star"); + contents->AddChildView(button); + button->SetBounds(5, 5, 50, 20); + button->SetID(kThumbnailStarID); + button = new ChromeViews::NativeButton(L"SuperStar"); + contents->AddChildView(button); + button->SetBounds(60, 5, 100, 20); + button->SetID(kThumbnailSuperStarID); + + contents_->AddChildView(contents); + contents->SetBounds(200, y, 200, 50); +} + +// WindowDelegate Implementation. +std::wstring TestViewWindow::GetWindowTitle() const { + return L"Focus Manager Test Window"; +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusManagerTest +//////////////////////////////////////////////////////////////////////////////// + +FocusManagerTest::FocusManagerTest() { +} + +FocusManagerTest::~FocusManagerTest() { +} + +TestViewWindow* FocusManagerTest::GetWindow() { + return test_window_; +} + +void FocusManagerTest::SetUp() { + test_window_ = new TestViewWindow(this); + test_window_->Init(); + test_window_->Show(); +} + +void FocusManagerTest::TearDown() { + test_window_->CloseNow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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 ChromeViews::AcceleratorHandler()); + + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(test_window_->GetHWND()); + // 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); + ChromeViews::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. + ChromeViews::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); + ChromeViews::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. + ChromeViews::RootView* root_view = test_window_->GetContentsRootView(); + for (int i = 0; i < arraysize(kMainContentsDisabledIDs); i++) { + ChromeViews::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++) { + ChromeViews::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++) { + ChromeViews::View* v = + root_view->GetViewByID(kSearchContentsDisabledIDs[i]); + ASSERT_TRUE(v != NULL); + if (v) + v->SetEnabled(false); + } + + + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(test_window_->GetHWND()); + ChromeViews::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/chrome/views/grid_layout.cc b/chrome/views/grid_layout.cc new file mode 100644 index 0000000..02d1856 --- /dev/null +++ b/chrome/views/grid_layout.cc @@ -0,0 +1,1037 @@ +// 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 "chrome/views/grid_layout.h" + +#include <algorithm> + +#include "base/logging.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +// LayoutElement ------------------------------------------------------ + +// A LayoutElement has a size and location along one axis. It contains +// methods that are used along both axis. +class LayoutElement { + public: + // Invokes ResetSize on all the layout elements. + template <class T> + static void ResetSizes(std::vector<T*>* elements) { + // Reset the layout width of each column. + for (std::vector<T*>::iterator i = elements->begin(); + i != elements->end(); ++i) { + (*i)->ResetSize(); + } + } + + // Sets the location of each element to be the sum of the sizes of the + // preceding elements. + template <class T> + static void CalculateLocationsFromSize(std::vector<T*>* elements) { + // Reset the layout width of each column. + int location = 0; + for (std::vector<T*>::iterator i = elements->begin(); + i != elements->end(); ++i) { + (*i)->SetLocation(location); + location += (*i)->Size(); + } + } + + // Distributes delta among the resizable elements. + // Each resizable element is given ResizePercent / total_percent * delta + // pixels extra of space. + template <class T> + static void DistributeDelta(int delta, std::vector<T*>* elements) { + if (delta == 0) + return; + + float total_percent = 0; + int resize_count = 0; + for (std::vector<T*>::iterator i = elements->begin(); + i != elements->end(); ++i) { + total_percent += (*i)->ResizePercent(); + resize_count++; + } + if (total_percent == 0) { + // None of the elements are resizable, return. + return; + } + int remaining = delta; + int resized = resize_count; + for (std::vector<T*>::iterator i = elements->begin(); + i != elements->end(); ++i) { + T* element = *i; + if (element->ResizePercent() > 0) { + int to_give; + if (--resized == 0) { + to_give = remaining; + } else { + to_give = static_cast<int>(delta * + (element->resize_percent_ / total_percent)); + remaining -= to_give; + } + element->SetSize(element->Size() + to_give); + } + } + } + + // Returns the sum of the size of the elements from start to start + length. + template <class T> + static int TotalSize(int start, int length, std::vector<T*>* elements) { + DCHECK(start >= 0 && length > 0 && + start + length <= static_cast<int>(elements->size())); + int size = 0; + for (int i = start, max = start + length; i < max; ++i) { + size += (*elements)[i]->Size(); + } + return size; + } + + explicit LayoutElement(float resize_percent) + : resize_percent_(resize_percent) { + DCHECK(resize_percent >= 0); + } + + virtual ~LayoutElement() {} + + void SetLocation(int location) { + location_ = location; + } + + int Location() { + return location_; + } + + // Adjusts the size of this LayoutElement to be the max of the current size + // and the specified size. + virtual void AdjustSize(int size) { + size_ = std::max(size_, size); + } + + // Resets the size to the initial size. This sets the size to 0, but + // subclasses that have a different initial size should override. + virtual void ResetSize() { + SetSize(0); + } + + void SetSize(int size) { + size_ = size; + } + + int Size() { + return size_; + } + + void SetResizePercent(float percent) { + resize_percent_ = percent; + } + + float ResizePercent() { + return resize_percent_; + } + + bool IsResizable() { + return resize_percent_ > 0; + } + + private: + float resize_percent_; + int location_; + int size_; + + DISALLOW_EVIL_CONSTRUCTORS(LayoutElement); +}; + +// Column ------------------------------------------------------------- + +// As the name implies, this represents a Column. Column contains default +// values for views originating in this column. +class Column : public LayoutElement { + public: + Column(GridLayout::Alignment h_align, + GridLayout::Alignment v_align, + float resize_percent, + GridLayout::SizeType size_type, + int fixed_width, + int min_width, + size_t index, + bool is_padding) + : LayoutElement(resize_percent), + h_align_(h_align), + v_align_(v_align), + size_type_(size_type), + same_size_column_(-1), + fixed_width_(fixed_width), + min_width_(min_width), + index_(index), + is_padding_(is_padding), + master_column_(NULL) {} + + virtual ~Column() {} + + GridLayout::Alignment h_align() { return h_align_; } + GridLayout::Alignment v_align() { return v_align_; } + + virtual void ResetSize(); + + private: + friend class ColumnSet; + friend class GridLayout; + + Column* GetLastMasterColumn(); + + // Determines the max size of all linked columns, and sets each column + // to that size. This should only be used for the master column. + void UnifySameSizedColumnSizes(); + + virtual void AdjustSize(int size); + + const GridLayout::Alignment h_align_; + const GridLayout::Alignment v_align_; + const GridLayout::SizeType size_type_; + int same_size_column_; + const int fixed_width_; + const int min_width_; + + // Index of this column in the ColumnSet. + const size_t index_; + + const bool is_padding_; + + // If multiple columns have their sizes linked, one is the + // master column. The master column is identified by the + // master_column field being equal to itself. The master columns + // same_size_columns field contains the set of Columns with the + // the same size. Columns who are linked to other columns, but + // are not the master column have their master_column pointing to + // one of the other linked columns. Use the method GetLastMasterColumn + // to resolve the true master column. + std::vector<Column*> same_size_columns_; + Column* master_column_; + + DISALLOW_EVIL_CONSTRUCTORS(Column); +}; + +void Column::ResetSize() { + if (size_type_ == GridLayout::FIXED) { + SetSize(fixed_width_); + } else { + SetSize(min_width_); + } +} + +Column* Column::GetLastMasterColumn() { + if (master_column_ == NULL) { + return NULL; + } + if (master_column_ == this) { + return this; + } + return master_column_->GetLastMasterColumn(); +} + +void Column::UnifySameSizedColumnSizes() { + DCHECK(master_column_ == this); + + // Accumulate the size first. + int size = 0; + for (std::vector<Column*>::iterator i = same_size_columns_.begin(); + i != same_size_columns_.end(); ++i) { + size = std::max(size, (*i)->Size()); + } + + // Then apply it. + for (std::vector<Column*>::iterator i = same_size_columns_.begin(); + i != same_size_columns_.end(); ++i) { + (*i)->SetSize(size); + } +} + +void Column::AdjustSize(int size) { + if (size_type_ == GridLayout::USE_PREF) + LayoutElement::AdjustSize(size); +} + +// Row ------------------------------------------------------------- + +class Row : public LayoutElement { + public: + Row(bool fixed_height, int height, float resize_percent, + ColumnSet* column_set) + : LayoutElement(resize_percent), + fixed_height_(fixed_height), + height_(height), + column_set_(column_set) { + } + + virtual ~Row() {} + + virtual void ResetSize() { + SetSize(height_); + } + + ColumnSet* column_set() { + return column_set_; + } + + private: + const bool fixed_height_; + const int height_; + // The column set used for this row; null for padding rows. + ColumnSet* column_set_; + + DISALLOW_EVIL_CONSTRUCTORS(Row); +}; + +// ViewState ------------------------------------------------------------- + +// Identifies the location in the grid of a particular view, along with +// placement information and size information. +struct ViewState { + ViewState(ColumnSet* column_set, View* view, int start_col, int start_row, + int col_span, int row_span, GridLayout::Alignment h_align, + GridLayout::Alignment v_align, int pref_width, int pref_height) + : column_set(column_set), + view(view), + start_col(start_col), + start_row(start_row), + col_span(col_span), + row_span(row_span), + h_align(h_align), + v_align(v_align), + pref_width_fixed(pref_width > 0), + pref_height_fixed(pref_height > 0), + pref_width(pref_width), + pref_height(pref_height), + remaining_width(0), + remaining_height(0) { + DCHECK(view && start_col >= 0 && start_row >= 0 && col_span > 0 && + row_span > 0 && start_col < column_set->num_columns() && + (start_col + col_span) <= column_set->num_columns()); + } + + ColumnSet* const column_set; + View* const view; + const int start_col; + const int start_row; + const int row_span; + const int col_span; + const GridLayout::Alignment h_align; + const GridLayout::Alignment v_align; + + // If true, the pref_width/pref_height were explicitly set and the view's + // preferred size is ignored. + const bool pref_width_fixed; + const bool pref_height_fixed; + + // The preferred width/height. These are reset during the layout process. + int pref_width; + int pref_height; + + // Used during layout. Gives how much width/height has not yet been + // distributed to the columns/rows the view is in. + int remaining_width; + int remaining_height; +}; + +static bool CompareByColumnSpan(const ViewState* v1, const ViewState* v2) { + return v1->col_span < v2->col_span; +} + +static bool CompareByRowSpan(const ViewState* v1, const ViewState* v2) { + return v1->row_span < v2->row_span; +} + +// ColumnSet ------------------------------------------------------------- + +ColumnSet::ColumnSet(int id) : id_(id) { +} + +ColumnSet::~ColumnSet() { + for (std::vector<Column*>::iterator i = columns_.begin(); + i != columns_.end(); ++i) { + delete *i; + } +} + +void ColumnSet::AddPaddingColumn(float resize_percent, int width) { + AddColumn(GridLayout::FILL, GridLayout::FILL, resize_percent, + GridLayout::FIXED, width, width, true); +} + +void ColumnSet::AddColumn(GridLayout::Alignment h_align, + GridLayout::Alignment v_align, + float resize_percent, + GridLayout::SizeType size_type, + int fixed_width, + int min_width) { + AddColumn(h_align, v_align, resize_percent, size_type, fixed_width, + min_width, false); +} + + +void ColumnSet::LinkColumnSizes(int first, ...) { + va_list marker; + va_start(marker, first); + DCHECK(first >= 0 && first < num_columns()); + for (int last = first, next = va_arg(marker, int); next != -1; + next = va_arg(marker, int)) { + DCHECK(next >= 0 && next < num_columns()); + columns_[last]->same_size_column_ = next; + last = next; + } + va_end(marker); +} + +void ColumnSet::AddColumn(GridLayout::Alignment h_align, + GridLayout::Alignment v_align, + float resize_percent, + GridLayout::SizeType size_type, + int fixed_width, + int min_width, + bool is_padding) { + Column* column = new Column(h_align, v_align, resize_percent, size_type, + fixed_width, min_width, columns_.size(), + is_padding); + columns_.push_back(column); +} + +void ColumnSet::AddViewState(ViewState* view_state) { + // view_states are ordered by column_span (in ascending order). + std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(), + view_states_.end(), + view_state, + CompareByColumnSpan); + view_states_.insert(i, view_state); +} + +void ColumnSet::CalculateMasterColumns() { + for (std::vector<Column*>::iterator i = columns_.begin(); + i != columns_.end(); ++i) { + Column* column = *i; + int same_size_column_index = column->same_size_column_; + if (same_size_column_index != -1) { + DCHECK(same_size_column_index >= 0 && + same_size_column_index < static_cast<int>(columns_.size())); + Column* master_column = column->master_column_; + Column* same_size_column = columns_[same_size_column_index]; + Column* same_size_column_master = same_size_column->master_column_; + if (master_column == NULL) { + // Current column is not linked to any other column. + if (same_size_column_master == NULL) { + // Both columns are not linked. + column->master_column_ = column; + same_size_column->master_column_ = column; + column->same_size_columns_.push_back(same_size_column); + column->same_size_columns_.push_back(column); + } else { + // Column to link to is linked with other columns. + // Add current column to list of linked columns in other columns + // master column. + same_size_column->GetLastMasterColumn()-> + same_size_columns_.push_back(column); + // And update the master column for the current column to that + // of the same sized column. + column->master_column_ = same_size_column; + } + } else { + // Current column is already linked with another column. + if (same_size_column_master == NULL) { + // Column to link with is not linked to any other columns. + // Update it's master_column. + same_size_column->master_column_ = column; + // Add linked column to list of linked column. + column->GetLastMasterColumn()->same_size_columns_. + push_back(same_size_column); + } else if (column->GetLastMasterColumn() != + same_size_column->GetLastMasterColumn()) { + // The two columns are already linked with other columns. + std::vector<Column*>* same_size_columns = + &(column->GetLastMasterColumn()->same_size_columns_); + std::vector<Column*>* other_same_size_columns = + &(same_size_column->GetLastMasterColumn()->same_size_columns_); + // Add all the columns from the others master to current columns + // master. + same_size_columns->insert(same_size_columns->end(), + other_same_size_columns->begin(), + other_same_size_columns->end()); + // The other master is no longer a master, clear its vector of + // linked columns, and reset its master_column. + other_same_size_columns->clear(); + same_size_column->GetLastMasterColumn()->master_column_ = column; + } + } + } + } + AccumulateMasterColumns(); +} + +void ColumnSet::AccumulateMasterColumns() { + DCHECK(master_columns_.empty()); + for (std::vector<Column*>::iterator i = columns_.begin(); + i != columns_.end(); ++i) { + Column* column = *i; + Column* master_column = column->GetLastMasterColumn(); + if (master_column && + find(master_columns_.begin(), master_columns_.end(), + master_column) == master_columns_.end()) { + master_columns_.push_back(master_column); + } + // At this point, GetLastMasterColumn may not == master_column + // (may have to go through a few Columns)_. Reset master_column to + // avoid hops. + column->master_column_ = master_column; + } +} + +void ColumnSet::UnifySameSizedColumnSizes() { + for (std::vector<Column*>::iterator i = master_columns_.begin(); + i != master_columns_.end(); ++i) { + (*i)->UnifySameSizedColumnSizes(); + } +} + +void ColumnSet::UpdateRemainingWidth(ViewState* view_state) { + for (int i = view_state->start_col; i < view_state->col_span; ++i) { + view_state->remaining_width -= columns_[i]->Size(); + } +} + +void ColumnSet::DistributeRemainingWidth(ViewState* view_state) { + // This is nearly the same as that for rows, but differs in so far as how + // Rows and Columns are treated. Rows have two states, resizable or not. + // Columns have three, resizable, USE_PREF or not resizable. This results + // in slightly different handling for distributing unaccounted size. + int width = view_state->remaining_width; + if (width <= 0) { + // The columns this view is in are big enough to accommodate it. + return; + } + + // Determine which columns are resizable, and which have a size type + // of USE_PREF. + int resizable_columns = 0; + int pref_size_columns = 0; + int start_col = view_state->start_col; + int max_col = view_state->start_col + view_state->col_span; + for (int i = start_col; i < max_col; ++i) { + if (columns_[i]->IsResizable()) { + resizable_columns++; + } else if (columns_[i]->size_type_ == GridLayout::USE_PREF) { + pref_size_columns++; + } + } + + if (resizable_columns > 0) { + // There are resizable columns, give the remaining width to them. + int to_distribute = width / resizable_columns; + for (int i = start_col; i < max_col; ++i) { + if (columns_[i]->IsResizable()) { + width -= to_distribute; + if (width < to_distribute) { + // Give all slop to the last column. + to_distribute += width; + } + columns_[i]->SetSize(columns_[i]->Size() + to_distribute); + } + } + } else if (pref_size_columns > 0) { + // None of the columns are resizable, distribute the width among those + // that use the preferred size. + int to_distribute = width / pref_size_columns; + for (int i = start_col; i < max_col; ++i) { + if (columns_[i]->size_type_ == GridLayout::USE_PREF) { + width -= to_distribute; + if (width < to_distribute) + to_distribute += width; + columns_[i]->SetSize(columns_[i]->Size() + to_distribute); + } + } + } +} + +int ColumnSet::LayoutWidth() { + int width = 0; + for (std::vector<Column*>::iterator i = columns_.begin(); + i != columns_.end(); ++i) { + width += (*i)->Size(); + } + return width; +} + +int ColumnSet::GetColumnWidth(int start_col, int col_span) { + return LayoutElement::TotalSize(start_col, col_span, &columns_); +} + +void ColumnSet::ResetColumnXCoordinates() { + LayoutElement::CalculateLocationsFromSize(&columns_); +} + +void ColumnSet::CalculateSize() { + CSize pref; + // Reset the preferred and remaining sizes. + for (std::vector<ViewState*>::iterator i = view_states_.begin(); + i != view_states_.end(); ++i) { + ViewState* view_state = *i; + pref.cx = pref.cy = 0; + if (!view_state->pref_width_fixed || !view_state->pref_height_fixed) { + view_state->view->GetPreferredSize(&pref); + if (!view_state->pref_width_fixed) + view_state->pref_width = pref.cx; + if (!view_state->pref_height_fixed) + view_state->pref_height = pref.cy; + } + view_state->remaining_width = pref.cx; + view_state->remaining_height = pref.cy; + } + + // Let layout element reset the sizes for us. + LayoutElement::ResetSizes(&columns_); + + // Distribute the size of each view with a col span == 1. + std::vector<ViewState*>::iterator view_state_iterator = + view_states_.begin(); + for (; view_state_iterator != view_states_.end() && + (*view_state_iterator)->col_span == 1; ++view_state_iterator) { + ViewState* view_state = *view_state_iterator; + Column* column = columns_[view_state->start_col]; + column->AdjustSize(view_state->pref_width); + view_state->remaining_width -= column->Size(); + } + + // Make sure all linked columns have the same size. + UnifySameSizedColumnSizes(); + + // Distribute the size of each view with a column span > 1. + for (; view_state_iterator != view_states_.end(); ++view_state_iterator) { + ViewState* view_state = *view_state_iterator; + + // Update the remaining_width from columns this view_state touches. + UpdateRemainingWidth(view_state); + + // Distribute the remaining width. + DistributeRemainingWidth(view_state); + + // Update the size of linked columns. + // This may need to be combined with previous step. + UnifySameSizedColumnSizes(); + } +} + +void ColumnSet::Resize(int delta) { + LayoutElement::DistributeDelta(delta, &columns_); +} + +// GridLayout ------------------------------------------------------------- + +GridLayout::GridLayout(View* host) + : host_(host), + calculated_master_columns_(false), + remaining_row_span_(0), + current_row_(-1), + next_column_(0), + current_row_col_set_(NULL), + top_inset_(0), + bottom_inset_(0), + left_inset_(0), + right_inset_(0), + adding_view_(false) { + DCHECK(host); +} + +GridLayout::~GridLayout() { + for (std::vector<ColumnSet*>::iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + delete *i; + } + for (std::vector<ViewState*>::iterator i = view_states_.begin(); + i != view_states_.end(); ++i) { + delete *i; + } + for (std::vector<Row*>::iterator i = rows_.begin(); + i != rows_.end(); ++i) { + delete *i; + } +} + +void GridLayout::SetInsets(int top, int left, int bottom, int right) { + top_inset_ = top; + bottom_inset_ = bottom; + left_inset_ = left; + right_inset_ = right; +} + +ColumnSet* GridLayout::AddColumnSet(int id) { + DCHECK(GetColumnSet(id) == NULL); + ColumnSet* column_set = new ColumnSet(id); + column_sets_.push_back(column_set); + return column_set; +} + +void GridLayout::StartRowWithPadding(float vertical_resize, int column_set_id, + float padding_resize, int padding) { + AddPaddingRow(padding_resize, padding); + StartRow(vertical_resize, column_set_id); +} + +void GridLayout::StartRow(float vertical_resize, int column_set_id) { + ColumnSet* column_set = GetColumnSet(column_set_id); + DCHECK(column_set); + AddRow(new Row(false, 0, vertical_resize, column_set)); +} + +void GridLayout::AddPaddingRow(float vertical_resize, int pixel_count) { + AddRow(new Row(true, pixel_count, vertical_resize, NULL)); +} + +void GridLayout::SkipColumns(int col_count) { + DCHECK(col_count > 0); + next_column_ += col_count; + DCHECK(current_row_col_set_ && + next_column_ <= current_row_col_set_->num_columns()); + SkipPaddingColumns(); +} + +void GridLayout::AddView(View* view) { + AddView(view, 1, 1); +} + +void GridLayout::AddView(View* view, int col_span, int row_span) { + DCHECK(current_row_col_set_ && + next_column_ < current_row_col_set_->num_columns()); + Column* column = current_row_col_set_->columns_[next_column_]; + AddView(view, col_span, row_span, column->h_align(), column->v_align()); +} + +void GridLayout::AddView(View* view, int col_span, int row_span, + Alignment h_align, Alignment v_align) { + AddView(view, col_span, row_span, h_align, v_align, 0, 0); +} + +void GridLayout::AddView(View* view, int col_span, int row_span, + Alignment h_align, Alignment v_align, + int pref_width, int pref_height) { + DCHECK(current_row_col_set_ && col_span > 0 && row_span > 0 && + (next_column_ + col_span) <= current_row_col_set_->num_columns()); + ViewState* state = + new ViewState(current_row_col_set_, view, next_column_, current_row_, + col_span, row_span, h_align, v_align, pref_width, + pref_height); + AddViewState(state); +} + +static void CalculateSize(int pref_size, GridLayout::Alignment alignment, + int* location, int* size) { + if (alignment != GridLayout::FILL) { + int available_size = *size; + *size = std::min(*size, pref_size); + switch (alignment) { + case GridLayout::LEADING: + // Nothing to do, location already points to start. + break; + case GridLayout::CENTER: + *location += (available_size - *size) / 2; + break; + case GridLayout::TRAILING: + *location = *location + available_size - *size; + break; + default: + NOTREACHED(); + } + } +} + +void GridLayout::Installed(View* host) { + DCHECK(host_ == host); +} + +void GridLayout::Uninstalled(View* host) { + DCHECK(host_ == host); +} + +void GridLayout::ViewAdded(View* host, View* view) { + DCHECK(host_ == host && adding_view_); +} + +void GridLayout::ViewRemoved(View* host, View* view) { + DCHECK(host_ == host); +} + +void GridLayout::Layout(View* host) { + DCHECK(host_ == host); + // SizeRowsAndColumns sets the size and location of each row/column, but + // not of the views. + CSize pref; + SizeRowsAndColumns(true, host_->GetWidth(), host_->GetHeight(), &pref); + + // Size each view. + for (std::vector<ViewState*>::iterator i = view_states_.begin(); + i != view_states_.end(); ++i) { + ViewState* view_state = *i; + ColumnSet* column_set = view_state->column_set; + View* view = (*i)->view; + DCHECK(view); + int x = column_set->columns_[view_state->start_col]->Location() + + left_inset_; + int width = column_set->GetColumnWidth(view_state->start_col, + view_state->col_span); + CalculateSize(view_state->pref_width, view_state->h_align, + &x, &width); + int y = rows_[view_state->start_row]->Location() + top_inset_; + int height = LayoutElement::TotalSize(view_state->start_row, + view_state->row_span, &rows_); + CalculateSize(view_state->pref_height, view_state->v_align, + &y, &height); + view->SetBounds(x, y, width, height); + } +} + +void GridLayout::GetPreferredSize(View* host, CSize* out) { + DCHECK(host_ == host); + SizeRowsAndColumns(false, 0, 0, out); +} + +int GridLayout::GetPreferredHeightForWidth(View* host, int width) { + DCHECK(host_ == host); + CSize pref; + SizeRowsAndColumns(false, width, 0, &pref); + return pref.cy; +} + +void GridLayout::SizeRowsAndColumns(bool layout, int width, int height, + CSize* pref) { + // Make sure the master columns have been calculated. + CalculateMasterColumnsIfNecessary(); + pref->cx = pref->cy = 0; + if (rows_.empty()) + return; + + // Calculate the size of each of the columns. Some views preferred heights are + // derived from their width, as such we need to calculate the size of the + // columns first. + for (std::vector<ColumnSet*>::iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + (*i)->CalculateSize(); + if (layout || width > 0) { + // We're doing a layout, divy up any extra space. + (*i)->Resize(width - (*i)->LayoutWidth() - left_inset_ - right_inset_); + // And reset the x coordinates. + (*i)->ResetColumnXCoordinates(); + } + pref->cx = std::max(static_cast<int>(pref->cx), (*i)->LayoutWidth()); + } + pref->cx += left_inset_ + right_inset_; + + // Reset the height of each row. + LayoutElement::ResetSizes(&rows_); + + // Do two things: + // . Reset the remaining_height of each view state. + // . If the width the view will be given is different than it's pref, ask + // for the height given a particularly width. + for (std::vector<ViewState*>::iterator i= view_states_.begin(); + i != view_states_.end() ; ++i) { + ViewState* view_state = *i; + view_state->remaining_height = view_state->pref_height; + if (view_state->h_align == FILL) { + // The view is resizable. As the pref height may vary with the width, + // ask for the pref again. + int actual_width = + view_state->column_set->GetColumnWidth(view_state->start_col, + view_state->col_span); + if (actual_width != view_state->pref_width && + !view_state->pref_height_fixed) { + // The width this view will get differs from it's preferred. Some Views + // pref height varies with it's width; ask for the preferred again. + view_state->pref_height = + view_state->view->GetHeightForWidth(actual_width); + view_state->remaining_height = view_state->pref_height; + } + } + } + + // Update the height of each row from the views. + std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin(); + for (; view_states_iterator != view_states_.end() && + (*view_states_iterator)->row_span == 1; ++view_states_iterator) { + ViewState* view_state = *view_states_iterator; + Row* row = rows_[view_state->start_row]; + row->AdjustSize(view_state->remaining_height); + view_state->remaining_height = 0; + } + + // Distribute the height of each view with a row span > 1. + for (; view_states_iterator != view_states_.end(); ++view_states_iterator) { + ViewState* view_state = *view_states_iterator; + + // Update the remaining_width from columns this view_state touches. + UpdateRemainingHeightFromRows(view_state); + + // Distribute the remaining height. + DistributeRemainingHeight(view_state); + } + + // Update the location of each of the rows. + LayoutElement::CalculateLocationsFromSize(&rows_); + + // We now know the preferred height, set it here. + pref->cy = rows_[rows_.size() - 1]->Location() + + rows_[rows_.size() - 1]->Size() + top_inset_ + bottom_inset_; + + if (layout && height != pref->cy) { + // We're doing a layout, and the height differs from the preferred height, + // divy up the extra space. + LayoutElement::DistributeDelta(height - pref->cy, &rows_); + + // Reset y locations. + LayoutElement::CalculateLocationsFromSize(&rows_); + } +} + +void GridLayout::CalculateMasterColumnsIfNecessary() { + if (!calculated_master_columns_) { + calculated_master_columns_ = true; + for (std::vector<ColumnSet*>::iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + (*i)->CalculateMasterColumns(); + } + } +} + +void GridLayout::AddViewState(ViewState* view_state) { + DCHECK(view_state->view && (view_state->view->GetParent() == NULL || + view_state->view->GetParent() == host_)); + if (!view_state->view->GetParent()) { + adding_view_ = true; + host_->AddChildView(view_state->view); + adding_view_ = false; + } + remaining_row_span_ = std::max(remaining_row_span_, view_state->row_span); + next_column_ += view_state->col_span; + current_row_col_set_->AddViewState(view_state); + // view_states are ordered by row_span (in ascending order). + std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(), + view_states_.end(), + view_state, + CompareByRowSpan); + view_states_.insert(i, view_state); + SkipPaddingColumns(); +} + +ColumnSet* GridLayout::GetColumnSet(int id) { + for (std::vector<ColumnSet*>::iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + if ((*i)->id_ == id) { + return *i; + } + } + return NULL; +} + +void GridLayout::AddRow(Row* row) { + current_row_++; + remaining_row_span_--; + DCHECK(remaining_row_span_ <= 0 || + row->column_set() == NULL || + row->column_set() == GetLastValidColumnSet()); + next_column_ = 0; + rows_.push_back(row); + current_row_col_set_ = row->column_set(); + SkipPaddingColumns(); +} + +void GridLayout::UpdateRemainingHeightFromRows(ViewState* view_state) { + for (int i = 0, start_row = view_state->start_row; + i < view_state->row_span; ++i) { + view_state->remaining_height -= rows_[i + start_row]->Size(); + } +} + +void GridLayout::DistributeRemainingHeight(ViewState* view_state) { + int height = view_state->remaining_height; + if (height <= 0) + return; + + // Determine the number of resizable rows the view touches. + int resizable_rows = 0; + int start_row = view_state->start_row; + int max_row = view_state->start_row + view_state->row_span; + for (int i = start_row; i < max_row; ++i) { + if (rows_[i]->IsResizable()) { + resizable_rows++; + } + } + + if (resizable_rows > 0) { + // There are resizable rows, give the remaining height to them. + int to_distribute = height / resizable_rows; + for (int i = start_row; i < max_row; ++i) { + if (rows_[i]->IsResizable()) { + height -= to_distribute; + if (height < to_distribute) { + // Give all slop to the last column. + to_distribute += height; + } + rows_[i]->SetSize(rows_[i]->Size() + to_distribute); + } + } + } else { + // None of the rows are resizable, divy the remaining height up equally + // among all rows the view touches. + int each_row_height = height / view_state->row_span; + for (int i = start_row; i < max_row; ++i) { + height -= each_row_height; + if (height < each_row_height) + each_row_height += height; + rows_[i]->SetSize(rows_[i]->Size() + each_row_height); + } + view_state->remaining_height = 0; + } +} + +void GridLayout::SkipPaddingColumns() { + if (!current_row_col_set_) + return; + while (next_column_ < current_row_col_set_->num_columns() && + current_row_col_set_->columns_[next_column_]->is_padding_) { + next_column_++; + } +} + +ColumnSet* GridLayout::GetLastValidColumnSet() { + for (int i = current_row_ - 1; i >= 0; --i) { + if (rows_[i]->column_set()) + return rows_[i]->column_set(); + } + return NULL; +} + +} // namespace diff --git a/chrome/views/grid_layout.h b/chrome/views/grid_layout.h new file mode 100644 index 0000000..82ad373 --- /dev/null +++ b/chrome/views/grid_layout.h @@ -0,0 +1,378 @@ +// 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. + +#ifndef CHROME_VIEWS_GRID_LAYOUT_H__ +#define CHROME_VIEWS_GRID_LAYOUT_H__ + +#include <string> +#include <vector> + +#include "chrome/views/layout_manager.h" +#include "chrome/views/view.h" + +// GridLayout is a LayoutManager that positions child Views in a grid. You +// define the structure of the Grid first, then add the Views. +// The following creates a trivial grid with two columns separated by +// a column with padding: +// ColumnSet* columns = layout->AddColumnSet(0); // Give this column an +// // identifier of 0. +// columns->AddColumn(FILL, // Views are horizontally resized to fill column. +// FILL, // Views starting in this column are vertically +// // resized. +// 1, // This column has a resize weight of 1. +// USE_PREF, // Use the preferred size of the view. +// 0, // Ignored for USE_PREF. +// 0); // A minimum width of 0. +// columns->AddPaddingColumn(0, // The padding column is not resizable. +// 10); // And has a width of 10 pixels. +// columns->AddColumn(FILL, FILL, 0, USE_PREF, 0, 0); +// Now add the views: +// // First start a row. +// layout->StartRow(0, // This row isn't vertically resizable. +// 0); // The column set to use for this row. +// layout->AddView(v1); +// Notice you need not skip over padding columns, that's done for you. +// layout->AddView(v2); +// +// When adding a Column you give it the default alignment for all views +// originating in that column. You can override this for specific views +// when adding them. For example, the following forces a View to have +// a horizontal and vertical alignment of leading regardless of that defined +// for the column: +// layout->AddView(v1, 1, 1, LEADING, LEADING); +// +// If the View using GridLayout is given a size bigger than the preferred, +// columns and rows with a resize percent > 0 are resized. Each column/row +// is given resize_percent / total_resize_percent * extra_pixels extra +// pixels. Only Views with an Alignment of FILL are given extra space, others +// are aligned in the provided space. +// +// GridLayout allows you to define multiple column sets. When you start a +// new row you specify the id of the column set the row is to use. +// +// GridLayout allows you to force columns to have the same width. This is +// done using the LinkColumnSizes method. +// +// AddView takes care of adding the View to the View the GridLayout was +// created with. +namespace ChromeViews { + +class Column; +class ColumnSet; +class Row; +class View; + +struct ViewState; + +class GridLayout : public LayoutManager { + public: + // An enumeration of the possible alignments supported by GridLayout. + enum Alignment { + // Leading equates to left along the horizontal axis, and top along the + // vertical axis. + LEADING, + + // Centers the view along the axis. + CENTER, + + // Trailing equals to right along the horizontal axis, and bottom along + // the vertical axis. + TRAILING, + + // The view is resized to fill the space. + FILL + }; + + // An enumeration of the possible ways the size of a column may be obtained. + enum SizeType { + // The column size is fixed. + FIXED, + + // The preferred size of the view is used to determine the column size. + USE_PREF + }; + + explicit GridLayout(View* host); + virtual ~GridLayout(); + + // Sets the insets. All views are placed relative to these offsets. + void SetInsets(int top, int left, int bottom, int right); + + // Creates a new column set with the specified id and returns it. + // The id is later used when starting a new row. + // GridLayout takes ownership of the ColumnSet and will delete it when + // the GridLayout is deleted. + ColumnSet* AddColumnSet(int id); + + // Adds a padding row. Padding rows typically don't have any views, and + // but are used to provide vertical white space between views. + // Size specifies the height of the row. + void AddPaddingRow(float vertical_resize, int size); + + // A convenience for AddPaddingRow followed by StartRow. + void StartRowWithPadding(float vertical_resize, int column_set_id, + float padding_resize, int padding); + + // Starts a new row with the specified column set. + void StartRow(float vertical_resize, int column_set_id); + + // Advances past columns. Use this when the current column should not + // contain any views. + void SkipColumns(int col_count); + + // Adds a view using the default alignment from the column. The added + // view has a column and row span of 1. + // As a convenience this adds the view to the host. The view becomes owned + // by the host, and NOT this GridLayout. + void AddView(View* view); + + // Adds a view using the default alignment from the column. + // As a convenience this adds the view to the host. The view becomes owned + // by the host, and NOT this GridLayout. + void AddView(View* view, int col_span, int row_span); + + // Adds a view with the specified alignment and spans. + // As a convenience this adds the view to the host. The view becomes owned + // by the host, and NOT this GridLayout. + void AddView(View* view, int col_span, int row_span, Alignment h_align, + Alignment v_align); + + // Adds a view with the specified alignment and spans. If + // pref_width/pref_height is > 0 then the preferred width/height of the view + // is fixed to the specified value. + // As a convenience this adds the view to the host. The view becomes owned + // by the host, and NOT this GridLayout. + void AddView(View* view, int col_span, int row_span, + Alignment h_align, Alignment v_align, + int pref_width, int pref_height); + + // Notification we've been installed on a particular host. Checks that host + // is the same as the View supplied in the constructor. + virtual void Installed(View* host); + + // Notification we've been uninstalled on a particular host. Checks that host + // is the same as the View supplied in the constructor. + virtual void Uninstalled(View* host); + + // Notification that a view has been added. + virtual void ViewAdded(View* host, View* view); + + // Notification that a view has been removed. + virtual void ViewRemoved(View* host, View* view); + + // Layouts out the components. + virtual void Layout(View* host); + + // Returns the preferred size for the GridLayout. + virtual void GetPreferredSize(View* host, CSize* out); + + virtual int GetPreferredHeightForWidth(View* host, int width); + + private: + // As both Layout and GetPreferredSize need to do nearly the same thing, + // they both call into this method. This sizes the Columns/Rows as + // appropriate. If layout is true, width/height give the width/height the + // of the host, otherwise they are ignored. + void SizeRowsAndColumns(bool layout, int width, int height, CSize* pref); + + // Calculates the master columns of all the column sets. See Column for + // a description of what a master column is. + void CalculateMasterColumnsIfNecessary(); + + // This is called internally from AddView. It adds the ViewState to the + // appropriate structures, and updates internal fields such as next_column_. + void AddViewState(ViewState* view_state); + + // Returns the column set for the specified id, or NULL if one doesn't exist. + ColumnSet* GetColumnSet(int id); + + // Adds the Row to rows_, as well as updating next_column_, + // current_row_col_set ... + void AddRow(Row* row); + + // As the name says, updates the remaining_height of the ViewState for + // all Rows the supplied ViewState touches. + void UpdateRemainingHeightFromRows(ViewState* state); + + // If the view state's remaining height is > 0, it is distributed among + // the rows the view state touches. This is used during layout to make + // sure the Rows can accommodate a view. + void DistributeRemainingHeight(ViewState* state); + + // Advances next_column_ past any padding columns. + void SkipPaddingColumns(); + + // Returns the column set of the last non-padding row. + ColumnSet* GetLastValidColumnSet(); + + // The view we were created with. We don't own this. + View* const host_; + + // Whether or not we've calculated the master/linked columns. + bool calculated_master_columns_; + + // Used to verify a view isn't added with a row span that expands into + // another column structure. + int remaining_row_span_; + + // Current row. + int current_row_; + + // Current column. + int next_column_; + + // Column set for the current row. This is null for padding rows. + ColumnSet* current_row_col_set_; + + // Insets. + int top_inset_; + int bottom_inset_; + int left_inset_; + int right_inset_; + + // Set to true when adding a View. + bool adding_view_; + + // ViewStates. This is ordered by row_span in ascending order. + std::vector<ViewState*> view_states_; + + // ColumnSets. + std::vector<ColumnSet*> column_sets_; + + // Rows. + std::vector<Row*> rows_; + + DISALLOW_EVIL_CONSTRUCTORS(GridLayout); +}; + +// ColumnSet is used to define a set of columns. GridLayout may have any +// number of ColumnSets. You don't create a ColumnSet directly, instead +// use the AddColumnSet method of GridLayout. +class ColumnSet { + public: + ~ColumnSet(); + + // Adds a column for padding. When adding views, padding columns are + // automatically skipped. For example, if you create a column set with + // two columns separated by a padding column, the first AddView automatically + // skips past the padding column. That is, to add two views, do: + // layout->AddView(v1); layout->AddView(v2);, not: + // layout->AddView(v1); layout->SkipColumns(1); layout->AddView(v2); + void AddPaddingColumn(float resize_percent, int width); + + // Adds a column. The alignment gives the default alignment for views added + // with no explicit alignment. fixed_width gives a specific width for the + // column, and is only used if size_type == FIXED. min_width gives the + // minimum width for the column. + void AddColumn(GridLayout::Alignment h_align, + GridLayout::Alignment v_align, + float resize_percent, + GridLayout::SizeType size_type, + int fixed_width, + int min_width); + + // Forces the specified columns to have the same size. The size of + // linked columns is that of the max of the specified columns. This + // must end with -1. For example, the following forces the first and + // second column to have the same size: + // LinkColumnSizes(0, 1, -1); + void LinkColumnSizes(int first, ...); + + // ID of this ColumnSet. + int id() const { return id_; } + + int num_columns() { return static_cast<int>(columns_.size()); } + + private: + friend class GridLayout; + + explicit ColumnSet(int id); + + void AddColumn(GridLayout::Alignment h_align, + GridLayout::Alignment v_align, + float resize_percent, + GridLayout::SizeType size_type, + int fixed_width, + int min_width, + bool is_padding); + + void AddViewState(ViewState* view_state); + + // Set description of these. + void CalculateMasterColumns(); + void AccumulateMasterColumns(); + + // Sets the size of each linked column to be the same. + void UnifySameSizedColumnSizes(); + + // Updates the remaining width field of the ViewState from that of the + // columns the view spans. + void UpdateRemainingWidth(ViewState* view_state); + + // Makes sure the columns touched by view state are big enough for the + // view. + void DistributeRemainingWidth(ViewState* view_state); + + // Returns the total size needed for this ColumnSet. + int LayoutWidth(); + + // Returns the width of the specified columns. + int GetColumnWidth(int start_col, int col_span); + + // Updates the x coordinate of each column from the previous ones. + // NOTE: this doesn't include the insets. + void ResetColumnXCoordinates(); + + // Calculate the preferred width of each view in this column set, as well + // as updating the remaining_width. + void CalculateSize(); + + // Distributes delta amoung the resizable columns. + void Resize(int delta); + + // ID for this columnset. + const int id_; + + // The columns. + std::vector<Column*> columns_; + + // The ViewStates. This is sorted based on column_span in ascending + // order. + std::vector<ViewState*> view_states_; + + // The master column of those columns that are linked. See Column + // for a description of what the master column is. + std::vector<Column*> master_columns_; + + DISALLOW_EVIL_CONSTRUCTORS(ColumnSet); +}; + +} // namespace + +#endif // CHROME_VIEWS_GRID_LAYOUT_H__ diff --git a/chrome/views/grid_layout_unittest.cc b/chrome/views/grid_layout_unittest.cc new file mode 100644 index 0000000..85570d6 --- /dev/null +++ b/chrome/views/grid_layout_unittest.cc @@ -0,0 +1,540 @@ +// 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 "chrome/views/grid_layout.h" +#include "chrome/views/view.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; +using ChromeViews::View; + +static void ExpectViewBoundsEquals(int x, int y, int w, int h, + const View* view) { + EXPECT_EQ(x, view->GetX()); + EXPECT_EQ(y, view->GetY()); + EXPECT_EQ(w, view->GetWidth()); + EXPECT_EQ(h, view->GetHeight()); +} + +class SettableSizeView : public View { + public: + explicit SettableSizeView(const CSize& pref) { + pref_ = pref; + } + + virtual void GetPreferredSize(CSize *out) { + *out = pref_; + } + + private: + CSize pref_; +}; + +class GridLayoutTest : public testing::Test { + public: + virtual void SetUp() { + layout = new GridLayout(&host); + } + + virtual void TearDown() { + delete layout; + } + + virtual void RemoveAll() { + for (int i = host.GetChildViewCount() - 1; i >= 0; i--) { + host.RemoveChildView(host.GetChildViewAt(i)); + } + } + + void GetPreferredSize() { + layout->GetPreferredSize(&host, &pref); + } + + CSize pref; + CRect bounds; + View host; + GridLayout* layout; +}; + +class GridLayoutAlignmentTest : public testing::Test { + public: + GridLayoutAlignmentTest() : + host(), + v1(CSize(10, 20)), + layout(new GridLayout(&host)) {} + + virtual void SetUp() { + } + + virtual void TearDown() { + delete layout; + } + + virtual void RemoveAll() { + for (int i = host.GetChildViewCount() - 1; i >= 0; i--) { + host.RemoveChildView(host.GetChildViewAt(i)); + } + } + + void TestAlignment(GridLayout::Alignment alignment, CRect* bounds) { + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(alignment, alignment, 1, GridLayout::USE_PREF, 0, 0); + layout->StartRow(1, 0); + layout->AddView(&v1); + CSize pref; + layout->GetPreferredSize(&host, &pref); + EXPECT_TRUE(CSize(10, 20) == pref); + host.SetBounds(0, 0, 100, 100); + layout->Layout(&host); + v1.GetBounds(bounds); + RemoveAll(); + } + + View host; + SettableSizeView v1; + GridLayout* layout; +}; + +TEST_F(GridLayoutAlignmentTest, Fill) { + CRect bounds; + TestAlignment(GridLayout::FILL, &bounds); + EXPECT_TRUE(CRect(0, 0, 100, 100) == bounds); +} + +TEST_F(GridLayoutAlignmentTest, Leading) { + CRect bounds; + TestAlignment(GridLayout::LEADING, &bounds); + EXPECT_TRUE(CRect(0, 0, 10, 20) == bounds); +} + +TEST_F(GridLayoutAlignmentTest, Center) { + CRect bounds; + TestAlignment(GridLayout::CENTER, &bounds); + EXPECT_TRUE(CRect(45, 40, 55, 60) == bounds); +} + +TEST_F(GridLayoutAlignmentTest, Trailing) { + CRect bounds; + TestAlignment(GridLayout::TRAILING, &bounds); + EXPECT_TRUE(CRect(90, 80, 100, 100) == bounds); +} + +TEST_F(GridLayoutTest, TwoColumns) { + SettableSizeView v1(CSize(10, 20)); + SettableSizeView v2(CSize(20, 20)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1); + layout->AddView(&v2); + + GetPreferredSize(); + EXPECT_TRUE(CSize(30, 20) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 10, 20, &v1); + ExpectViewBoundsEquals(10, 0, 20, 20, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, ColSpan1) { + SettableSizeView v1(CSize(100, 20)); + SettableSizeView v2(CSize(10, 40)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 1, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1, 2, 1); + layout->StartRow(0, 0); + layout->AddView(&v2); + + GetPreferredSize(); + EXPECT_TRUE(CSize(100, 60) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 100, 20, &v1); + ExpectViewBoundsEquals(0, 20, 10, 40, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, ColSpan2) { + SettableSizeView v1(CSize(100, 20)); + SettableSizeView v2(CSize(10, 20)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 1, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1, 2, 1); + layout->StartRow(0, 0); + layout->SkipColumns(1); + layout->AddView(&v2); + + GetPreferredSize(); + EXPECT_TRUE(CSize(100, 40) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 100, 20, &v1); + ExpectViewBoundsEquals(90, 20, 10, 20, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, ColSpan3) { + SettableSizeView v1(CSize(100, 20)); + SettableSizeView v2(CSize(10, 20)); + SettableSizeView v3(CSize(10, 20)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1, 2, 1); + layout->StartRow(0, 0); + layout->AddView(&v2); + layout->AddView(&v3); + + GetPreferredSize(); + EXPECT_TRUE(CSize(100, 40) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 100, 20, &v1); + ExpectViewBoundsEquals(0, 20, 10, 20, &v2); + ExpectViewBoundsEquals(50, 20, 10, 20, &v3); + + RemoveAll(); +} + + +TEST_F(GridLayoutTest, ColSpan4) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + + SettableSizeView v1(CSize(10, 10)); + SettableSizeView v2(CSize(10, 10)); + SettableSizeView v3(CSize(25, 20)); + layout->StartRow(0, 0); + layout->AddView(&v1); + layout->AddView(&v2); + layout->StartRow(0, 0); + layout->AddView(&v3, 2, 1); + + GetPreferredSize(); + EXPECT_TRUE(CSize(25, 30) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 10, 10, &v1); + ExpectViewBoundsEquals(12, 0, 10, 10, &v2); + ExpectViewBoundsEquals(0, 10, 25, 20, &v3); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, SameSizeColumns) { + SettableSizeView v1(CSize(50, 20)); + SettableSizeView v2(CSize(10, 10)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + c1->LinkColumnSizes(0, 1, -1); + layout->StartRow(0, 0); + layout->AddView(&v1); + layout->AddView(&v2); + + CSize pref; + layout->GetPreferredSize(&host, &pref); + EXPECT_TRUE(CSize(100, 20) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 50, 20, &v1); + ExpectViewBoundsEquals(50, 0, 10, 10, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, HorizontalResizeTest1) { + SettableSizeView v1(CSize(50, 20)); + SettableSizeView v2(CSize(10, 10)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::FILL, GridLayout::LEADING, + 1, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1); + layout->AddView(&v2); + + host.SetBounds(0, 0, 110, 20); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 100, 20, &v1); + ExpectViewBoundsEquals(100, 0, 10, 10, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, HorizontalResizeTest2) { + SettableSizeView v1(CSize(50, 20)); + SettableSizeView v2(CSize(10, 10)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::FILL, GridLayout::LEADING, + 1, GridLayout::USE_PREF, 0, 0); + c1->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, + 1, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1); + layout->AddView(&v2); + + host.SetBounds(0, 0, 120, 20); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 80, 20, &v1); + ExpectViewBoundsEquals(110, 0, 10, 10, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, TestVerticalResize1) { + SettableSizeView v1(CSize(50, 20)); + SettableSizeView v2(CSize(10, 10)); + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::FILL, GridLayout::FILL, + 1, GridLayout::USE_PREF, 0, 0); + layout->StartRow(1, 0); + layout->AddView(&v1); + layout->StartRow(0, 0); + layout->AddView(&v2); + + GetPreferredSize(); + EXPECT_TRUE(CSize(50, 30) == pref); + + host.SetBounds(0, 0, 50, 100); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 50, 90, &v1); + ExpectViewBoundsEquals(0, 90, 50, 10, &v2); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, Insets) { + SettableSizeView v1(CSize(10, 20)); + ColumnSet* c1 = layout->AddColumnSet(0); + layout->SetInsets(1, 2, 3, 4); + c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, 0); + layout->AddView(&v1); + + GetPreferredSize(); + EXPECT_TRUE(CSize(16, 24) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(2, 1, 10, 20, &v1); + + RemoveAll(); +} + +TEST_F(GridLayoutTest, FixedSize) { + layout->SetInsets(2, 2, 2, 2); + + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + int column_count = 4; + int title_width = 100; + int row_count = 2; + int pref_width = 10; + int pref_height = 20; + + for (int i = 0; i < column_count; ++i) { + set->AddColumn(ChromeViews::GridLayout::CENTER, + ChromeViews::GridLayout::CENTER, + 0, + ChromeViews::GridLayout::FIXED, + title_width, + title_width); + } + + for (int row = 0; row < row_count; ++row) { + layout->StartRow(0, 0); + for (int col = 0; col < column_count; ++col) { + layout->AddView(new SettableSizeView(CSize(pref_width, pref_height))); + } + } + + layout->Layout(&host); + + for (int i = 0; i < column_count; ++i) { + for (int row = 0; row < row_count; ++row) { + View* view = host.GetChildViewAt(row * column_count + i); + ExpectViewBoundsEquals(2 + title_width * i + (title_width - pref_width) / 2, + 2 + pref_height * row, + pref_width, pref_height, view); + } + } + + GetPreferredSize(); + EXPECT_TRUE(CSize(column_count * title_width + 4, + row_count * pref_height + 4) == pref); +} + +TEST_F(GridLayoutTest, RowSpanWithPaddingRow) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(ChromeViews::GridLayout::CENTER, + ChromeViews::GridLayout::CENTER, + 0, + ChromeViews::GridLayout::FIXED, + 10, + 10); + + layout->StartRow(0, 0); + layout->AddView(new SettableSizeView(CSize(10, 10)), 1, 2); + layout->AddPaddingRow(0, 10); +} + +TEST_F(GridLayoutTest, RowSpan) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(ChromeViews::GridLayout::LEADING, + ChromeViews::GridLayout::LEADING, + 0, + ChromeViews::GridLayout::USE_PREF, + 0, + 0); + set->AddColumn(ChromeViews::GridLayout::LEADING, + ChromeViews::GridLayout::LEADING, + 0, + ChromeViews::GridLayout::USE_PREF, + 0, + 0); + + layout->StartRow(0, 0); + layout->AddView(new SettableSizeView(CSize(20, 10))); + layout->AddView(new SettableSizeView(CSize(20, 40)), 1, 2); + layout->StartRow(1, 0); + ChromeViews::View* s3 = new SettableSizeView(CSize(20, 10)); + layout->AddView(s3); + + GetPreferredSize(); + EXPECT_TRUE(CSize(40, 40) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 10, 20, 10, s3); +} + +TEST_F(GridLayoutTest, RowSpan2) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0,GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + layout->AddView(new SettableSizeView(CSize(20, 20))); + ChromeViews::View* s3 = new SettableSizeView(CSize(64, 64)); + layout->AddView(s3, 1, 3); + + layout->AddPaddingRow(0, 10); + + layout->StartRow(0, 0); + layout->AddView(new SettableSizeView(CSize(10, 20))); + + GetPreferredSize(); + EXPECT_TRUE(CSize(84, 64) == pref); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(20, 0, 64, 64, s3); +} + +TEST_F(GridLayoutTest, FixedViewWidth) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0,GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + View* view = new SettableSizeView(CSize(30, 40)); + layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 10, 0); + + GetPreferredSize(); + EXPECT_EQ(10, pref.cx); + EXPECT_EQ(40, pref.cy); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 10, 40, view); +} + +TEST_F(GridLayoutTest, FixedViewHeight) { + ChromeViews::ColumnSet* set = layout->AddColumnSet(0); + + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0, GridLayout::USE_PREF, 0, 0); + set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, + 0,GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + View* view = new SettableSizeView(CSize(30, 40)); + layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 0, 10); + + GetPreferredSize(); + EXPECT_EQ(30, pref.cx); + EXPECT_EQ(10, pref.cy); + + host.SetBounds(0, 0, pref.cx, pref.cy); + layout->Layout(&host); + ExpectViewBoundsEquals(0, 0, 30, 10, view); +} diff --git a/chrome/views/group_table_view.cc b/chrome/views/group_table_view.cc new file mode 100644 index 0000000..f0b7167 --- /dev/null +++ b/chrome/views/group_table_view.cc @@ -0,0 +1,192 @@ +// 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 "chrome/views/group_table_view.h" + +#include "base/message_loop.h" +#include "base/task.h" +#include "chrome/common/gfx/chrome_canvas.h" + +namespace ChromeViews { + +static const COLORREF kSeparatorLineColor = RGB(208, 208, 208); +static const int kSeparatorLineThickness = 1; + +const char GroupTableView::kViewClassName[] = "chrome/views/GroupTableView"; + +GroupTableView::GroupTableView(GroupTableModel* model, + const std::vector<TableColumn>& columns, + TableTypes table_type, + bool single_selection, + bool resizable_columns, + bool autosize_columns) + : TableView(model, columns, table_type, false, resizable_columns, + autosize_columns), + model_(model), + sync_selection_factory_(this) { +} + +GroupTableView::~GroupTableView() { +} + +void GroupTableView::SyncSelection() { + int index = 0; + int row_count = model_->RowCount(); + GroupRange group_range; + while (index < row_count) { + model_->GetGroupRangeForItem(index, &group_range); + if (group_range.length == 1) { + // No synching required for single items. + index++; + } else { + // We need to select the group if at least one item is selected. + bool should_select = false; + for (int i = group_range.start; + i < group_range.start + group_range.length; ++i) { + if (IsItemSelected(i)) { + should_select = true; + break; + } + } + if (should_select) { + for (int i = group_range.start; + i < group_range.start + group_range.length; ++i) { + SetSelectedState(i, true); + } + } + index += group_range.length; + } + } +} + +void GroupTableView::OnKeyDown(unsigned short virtual_keycode) { + // In a list view, multiple items can be selected but only one item has the + // focus. This creates a problem when the arrow keys are used for navigating + // between items in the list view. An example will make this more clear: + // + // Suppose we have 5 items in the list view, and three of these items are + // part of one group: + // + // Index0: ItemA (No Group) + // Index1: ItemB (GroupX) + // Index2: ItemC (GroupX) + // Index3: ItemD (GroupX) + // Index4: ItemE (No Group) + // + // When GroupX is selected (say, by clicking on ItemD with the mouse), + // GroupTableView::SyncSelection() will make sure ItemB, ItemC and ItemD are + // selected. Also, the item with the focus will be ItemD (simply because + // this is the item the user happened to click on). If then the UP arrow is + // pressed once, the focus will be switched to ItemC and not to ItemA and the + // end result is that we are stuck in GroupX even though the intention was to + // switch to ItemA. + // + // For that exact reason, we need to set the focus appropriately when we + // detect that one of the arrow keys is pressed. Thus, when it comes time + // for the list view control to actually switch the focus, the right item + // will be selected. + if ((virtual_keycode != VK_UP) && (virtual_keycode != VK_DOWN)) { + return; + } + + // We start by finding the index of the item with the focus. If no item + // currently has the focus, then this routine doesn't do anything. + int focused_index; + int row_count = model_->RowCount(); + for (focused_index = 0; focused_index < row_count; focused_index++) { + if (ItemHasTheFocus(focused_index)) { + break; + } + } + + if (focused_index == row_count) { + return; + } + DCHECK_LT(focused_index, row_count); + + // Nothing to do if the item which has the focus is not part of a group. + GroupRange group_range; + model_->GetGroupRangeForItem(focused_index, &group_range); + if (group_range.length == 1) { + return; + } + + // If the user pressed the UP key, then the focus should be set to the + // topmost element in the group. If the user pressed the DOWN key, the focus + // should be set to the bottommost element. + if (virtual_keycode == VK_UP) { + SetFocusOnItem(group_range.start); + } else { + DCHECK_EQ(virtual_keycode, VK_DOWN); + SetFocusOnItem(group_range.start + group_range.length - 1); + } +} + +void GroupTableView::OnSelectedStateChanged(int item, bool is_selected) { + // The goal is to make sure all items for a same group are in a consistent + // state in term of selection. When a user clicks an item, several selection + // messages are sent, possibly including unselecting all currently selected + // items. For that reason, we post a task to be performed later, after all + // selection messages have been processed. In the meantime we just ignore all + // selection notifications. + if (sync_selection_factory_.empty()) { + MessageLoop::current()->PostTask(FROM_HERE, + sync_selection_factory_.NewRunnableMethod( + &GroupTableView::SyncSelection)); + } + TableView::OnSelectedStateChanged(item, is_selected); +} + +// Draws the line separator betweens the groups. +void GroupTableView::PostPaint(int row, int column, bool selected, + const CRect& bounds, HDC hdc) { + GroupRange group_range; + model_->GetGroupRangeForItem(row, &group_range); + + // We always paint a vertical line at the end of the last cell. + HPEN hPen = CreatePen(PS_SOLID, kSeparatorLineThickness, kSeparatorLineColor); + HPEN hPenOld = (HPEN) SelectObject(hdc, hPen); + int x = static_cast<int>(bounds.right - kSeparatorLineThickness); + MoveToEx(hdc, x, bounds.top, NULL); + LineTo(hdc, x, bounds.bottom); + + // We paint a separator line after the last item of a group. + if (row == (group_range.start + group_range.length - 1)) { + int y = static_cast<int>(bounds.bottom - kSeparatorLineThickness); + MoveToEx(hdc, 0, y, NULL); + LineTo(hdc, bounds.Width(), y); + } + SelectObject(hdc, hPenOld); + DeleteObject(hPen); +} + +std::string GroupTableView::GetClassName() const { + return kViewClassName; +} +} // Namespace diff --git a/chrome/views/group_table_view.h b/chrome/views/group_table_view.h new file mode 100644 index 0000000..cf4a37c --- /dev/null +++ b/chrome/views/group_table_view.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef CHROME_VIEWS_GROUP_TABLE_VIEW_H__ +#define CHROME_VIEWS_GROUP_TABLE_VIEW_H__ + +#include "base/task.h" +#include "chrome/views/table_view.h" + +// The GroupTableView adds grouping to the TableView class. +// It allows to have groups of rows that act as a single row from the selection +// perspective. Groups are visually separated by a horizontal line. + +namespace ChromeViews { + +struct GroupRange { + int start; + int length; +}; + +// The model driving the GroupTableView. +class GroupTableModel : public TableModel { + public: + // Populates the passed range with the first row/last row (included) + // that this item belongs to. + virtual void GetGroupRangeForItem(int item, GroupRange* range) = 0; +}; + +class GroupTableView : public TableView { + public: + // The view class name. + static const char kViewClassName[]; + + GroupTableView(GroupTableModel* model, + const std::vector<TableColumn>& columns, + TableTypes table_type, bool single_selection, + bool resizable_columns, bool autosize_columns); + virtual ~GroupTableView(); + + virtual std::string GetClassName() const; + + protected: + // Notification from the ListView that the selected state of an item has + // changed. + void OnSelectedStateChanged(int item, bool is_selected); + + // Extra-painting required to draw the separator line between groups. + virtual bool ImplementPostPaint() { return true; } + virtual void PostPaint(int row, int column, bool selected, + const CRect& bounds, HDC device_context); + + // In order to make keyboard navigation possible (using the Up and Down + // keys), we must take action when an arrow key is pressed. The reason we + // need to process this message has to do with the manner in which the focus + // needs to be set on a group item when a group is selected. + virtual void OnKeyDown(unsigned short virtual_keycode); + + private: + // Make the selection of group consistent. + void SyncSelection(); + + GroupTableModel *model_; + + // A factory to make the selection consistent among groups. + ScopedRunnableMethodFactory<GroupTableView> sync_selection_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(GroupTableView); +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_GROUP_TABLE_VIEW_H__ diff --git a/chrome/views/hwnd_notification_source.h b/chrome/views/hwnd_notification_source.h new file mode 100644 index 0000000..f3ce48c --- /dev/null +++ b/chrome/views/hwnd_notification_source.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef CHROME_VIEWS_HWND_NOTIFICATION_SOURCE_H__ +#define CHROME_VIEWS_HWND_NOTIFICATION_SOURCE_H__ + +#include "chrome/common/notification_source.h" + +// Specialization of the Source class for HWND. This is needed as the Source +// class expects a pointer type. +template<> +class Source<HWND> : public NotificationSource { + public: + explicit Source(HWND hwnd) : NotificationSource(hwnd) {} + + explicit Source(const NotificationSource& other) + : NotificationSource(other) {} + + HWND operator->() const { return ptr(); } + HWND ptr() const { return static_cast<HWND>(ptr_); } +}; + +#endif // #define CHROME_VIEWS_HWND_NOTIFICATION_SOURCE_H__
\ No newline at end of file diff --git a/chrome/views/hwnd_view.cc b/chrome/views/hwnd_view.cc new file mode 100644 index 0000000..82b2893 --- /dev/null +++ b/chrome/views/hwnd_view.cc @@ -0,0 +1,227 @@ +// 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 "chrome/views/hwnd_view.h" + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/win_util.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/scroll_view.h" +#include "chrome/views/view_container.h" +#include "base/logging.h" + +namespace ChromeViews { + +static const char kViewClassName[] = "chrome/views/HWNDView"; + +HWNDView::HWNDView() : + hwnd_(0), + preferred_size_(0, 0), + installed_clip_(false), + fast_resize_(false), + focus_view_(NULL) { + // HWNDs are placed relative to the root. As such, we need to + // know when the position of any ancestor changes, or our visibility relative + // to other views changed as it'll effect our position relative to the root. + SetNotifyWhenVisibleBoundsInRootChanges(true); +} + +HWNDView::~HWNDView() { +} + +void HWNDView::Attach(HWND hwnd) { + DCHECK(hwnd_ == NULL); + hwnd_ = hwnd; + + // First hide the new window. We don't want anything to draw (like sub-hwnd + // borders), when we change the parent below. + ShowWindow(hwnd_, SW_HIDE); + + // Need to set the HWND's parent before changing its size to avoid flashing. + ::SetParent(hwnd_, GetViewContainer()->GetHWND()); + UpdateHWNDBounds(); + + // Register with the focus manager so the associated view is focused when the + // native control gets the focus. + FocusManager::InstallFocusSubclass(hwnd_, focus_view_ ? focus_view_ : this); +} + +void HWNDView::Detach() { + DCHECK(hwnd_); + FocusManager::UninstallFocusSubclass(hwnd_); + hwnd_ = NULL; + installed_clip_ = false; +} + +void HWNDView::SetAssociatedFocusView(View* view) { + DCHECK(!::IsWindow(hwnd_)); + focus_view_ = view; +} + +HWND HWNDView::GetHWND() const { + return hwnd_; +} + +void HWNDView::UpdateHWNDBounds() { + if (!hwnd_) + return; + + // Since HWNDs know nothing about the View hierarchy (they are direct children + // of the ViewContainer that hosts our View hierarchy) they need to be + // positioned in the coordinate system of the ViewContainer, not the current + // view. + CPoint top_left; + + top_left.x = top_left.y = 0; + ConvertPointToViewContainer(this, &top_left); + + gfx::Rect vis_bounds = GetVisibleBounds(); + bool visible = !vis_bounds.IsEmpty(); + + if (visible && !fast_resize_) { + if (vis_bounds.width() != bounds_.Width() || + vis_bounds.height() != bounds_.Height()) { + // Only a portion of the HWND is really visible. + int x = vis_bounds.x(); + int y = vis_bounds.y(); + HRGN clip_region = CreateRectRgn(x, y, x + vis_bounds.width(), + y + vis_bounds.height()); + // NOTE: SetWindowRgn owns the region (as well as the deleting the + // current region), as such we don't delete the old region. + SetWindowRgn(hwnd_, clip_region, FALSE); + installed_clip_ = true; + } else if (installed_clip_) { + // The whole HWND is visible but we installed a clip on the HWND, + // uninstall it. + SetWindowRgn(hwnd_, 0, FALSE); + installed_clip_ = false; + } + } + + if (visible) { + UINT swp_flags; + swp_flags = SWP_DEFERERASE | + SWP_NOACTIVATE | + SWP_NOCOPYBITS | + SWP_NOOWNERZORDER | + SWP_NOZORDER; + // Only send the SHOWWINDOW flag if we're invisible, to avoid flashing. + if (!::IsWindowVisible(hwnd_)) + swp_flags = (swp_flags | SWP_SHOWWINDOW) & ~SWP_NOREDRAW; + + if (fast_resize_) { + // In a fast resize, we move the window and clip it with SetWindowRgn. + CRect rect; + GetWindowRect(hwnd_, &rect); + ::SetWindowPos(hwnd_, 0, top_left.x, top_left.y, rect.Width(), + rect.Height(), swp_flags); + + HRGN clip_region = CreateRectRgn(0, 0, + bounds_.Width(), + bounds_.Height()); + SetWindowRgn(hwnd_, clip_region, FALSE); + installed_clip_ = true; + } else { + ::SetWindowPos(hwnd_, 0, top_left.x, top_left.y, bounds_.Width(), + bounds_.Height(), swp_flags); + } + } else if (::IsWindowVisible(hwnd_)) { + // The window is currently visible, but its clipped by another view. Hide + // it. + ::SetWindowPos(hwnd_, 0, 0, 0, 0, 0, + SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | + SWP_NOREDRAW | SWP_NOOWNERZORDER ); + } +} + +void HWNDView::DidChangeBounds(const CRect& previous, const CRect& current) { + UpdateHWNDBounds(); +} + +void HWNDView::VisibilityChanged(View* starting_from, bool is_visible) { + //UpdateHWNDBounds(); + if (IsVisibleInRootView()) + ::ShowWindow(hwnd_, SW_SHOW); + else + ::ShowWindow(hwnd_, SW_HIDE); +} + +void HWNDView::GetPreferredSize(CSize *out) { + out->cx = preferred_size_.cx; + out->cy = preferred_size_.cy; +} + +void HWNDView::SetPreferredSize(const CSize& size) { + preferred_size_ = size; +} + +void HWNDView::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (hwnd_) { + ViewContainer* vc = GetViewContainer(); + if (is_add && vc) { + HWND parent = ::GetParent(hwnd_); + HWND vc_hwnd = vc->GetHWND(); + if (parent != vc_hwnd) { + ::SetParent(hwnd_, vc_hwnd); + } + if (IsVisibleInRootView()) + ::ShowWindow(hwnd_, SW_SHOW); + else + ::ShowWindow(hwnd_, SW_HIDE); + UpdateHWNDBounds(); + } else if (!is_add) { + ::ShowWindow(hwnd_, SW_HIDE); + ::SetParent(hwnd_, NULL); + } + } +} + +void HWNDView::VisibleBoundsInRootChanged() { + UpdateHWNDBounds(); +} + +void HWNDView::Paint(ChromeCanvas* canvas) { + // On Vista, the area behind our window is black (due to the Aero Glass) + // this means that during a fast resize (where our content doesn't draw + // over the full size of our HWND, and the HWND background color + // doesn't show up), we need to cover that blackness with something so + // that fast resizes don't result in black flash. + // + // It would be nice if this used some approximation of the page's + // current background color. + if (installed_clip_ && win_util::ShouldUseVistaFrame()) + canvas->FillRectInt(SkColorSetRGB(255, 255, 255), 0, 0, + bounds_.Width(), bounds_.Height()); +} + +std::string HWNDView::GetClassName() const { + return kViewClassName; +} + +} diff --git a/chrome/views/hwnd_view.h b/chrome/views/hwnd_view.h new file mode 100644 index 0000000..1f0d4a8 --- /dev/null +++ b/chrome/views/hwnd_view.h @@ -0,0 +1,122 @@ +// 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. + +#ifndef CHROME_VIEWS_HWND_VIEW_H__ +#define CHROME_VIEWS_HWND_VIEW_H__ + +#include <string> + +#include "chrome/views/view.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// HWNDView class +// +// The HWNDView class hosts a native window handle (HWND) sizing it +// according to the bounds of the view. This is useful whenever you need to +// show a UI control that has a HWND (e.g. a native windows Edit control) +// within thew View hierarchy and benefit from the sizing/layout. +// +///////////////////////////////////////////////////////////////////////////// +class HWNDView : public View { + public: + HWNDView(); + virtual ~HWNDView(); + + virtual void GetPreferredSize(CSize *out); + + void SetPreferredSize(const CSize& size); + + // Attach a window handle to this View, making the window it represents + // subject to sizing according to this View's parent container's Layout + // Manager's sizing heuristics. + void Attach(HWND hwnd); + + // Detach the attached window handle. It will no longer be updated + void Detach(); + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void VisibilityChanged(View* starting_from, bool is_visible); + + HWND GetHWND() const; + + // Resize the hosted HWND to the bounds of this View. + void UpdateHWNDBounds(); + + virtual void Paint(ChromeCanvas* canvas); + + // Overridden from View. + virtual std::string GetClassName() const; + + // A HWNDView has an associated focus View so that the focus of the native + // control and of the View are kept in sync. In simple cases where the + // HWNDView directly wraps a native window as is, the associated view is this + // View. In other cases where the HWNDView is part of another view (such as + // TextField), the actual View is not the HWNDView and this method must be + // called to set that. + // This method must be called before Attach(). + void SetAssociatedFocusView(View* view); + + void set_fast_resize(bool fast_resize) { fast_resize_ = fast_resize; } + + protected: + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Notification that our visible bounds relative to the root has changed. + // This updates the bounds of the HWND. + virtual void VisibleBoundsInRootChanged(); + + private: + // The hosted window handle. + HWND hwnd_; + + // The preferred size of this View + CSize preferred_size_; + + // Have we installed a region on the HWND used to clip to only the visible + // portion of the HWND? + bool installed_clip_; + + // Fast resizing will move the hwnd and clip its window region, this will + // result in white areas and will not resize the content (so scrollbars + // will be all wrong and content will flow offscreen). Only use this + // when you're doing extremely quick, high-framerate vertical resizes + // and don't care about accuracy. Make sure you do a real resize at the + // end. USE WITH CAUTION. + bool fast_resize_; + + // The view that should be given focus when this HWNDView is focused. + View* focus_view_; +}; + +} + +#endif // CHROME_VIEWS_HWND_VIEW_H__ diff --git a/chrome/views/hwnd_view_container.cc b/chrome/views/hwnd_view_container.cc new file mode 100644 index 0000000..6aa2332 --- /dev/null +++ b/chrome/views/hwnd_view_container.cc @@ -0,0 +1,877 @@ +// 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 "chrome/views/hwnd_view_container.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/win_util.h" +#include "chrome/views/aero_tooltip_manager.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/hwnd_notification_source.h" +#include "chrome/views/root_view.h" + +namespace ChromeViews { + +static const DWORD kWindowDefaultChildStyle = + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; +static const DWORD kWindowDefaultStyle = WS_OVERLAPPEDWINDOW; +static const DWORD kWindowDefaultExStyle = 0; + +// Property used to link the HWND to its RootView. +static const wchar_t* const kRootViewWindowProperty = L"__ROOT_VIEW__"; + +bool SetRootViewForHWND(HWND hwnd, RootView* root_view) { + return ::SetProp(hwnd, kRootViewWindowProperty, root_view) ? true : false; +} + +RootView* GetRootViewForHWND(HWND hwnd) { + return reinterpret_cast<RootView*>(::GetProp(hwnd, kRootViewWindowProperty)); +} + +// Used to locate the HWNDViewContainer issuing the current Create. Only valid +// for the life of Create. +// +// This obviously assumes we only create HWNDViewContainers from the same +// thread, which is currently the case. +static HWNDViewContainer* instance_issuing_create = NULL; + +/////////////////////////////////////////////////////////////////////////////// +// FillLayout + +FillLayout::FillLayout() { +} + +FillLayout::~FillLayout() { +} + +void FillLayout::Layout(View* host) { + CRect bounds; + host->GetViewContainer()->GetBounds(&bounds, false); + if (host->GetChildViewCount() == 0) + return; + + View* frame_view = host->GetChildViewAt(0); + frame_view->SetBounds(CRect(CPoint(0, 0), bounds.Size())); +} + +void FillLayout::GetPreferredSize(View* host, CSize* out) { + DCHECK(host->GetChildViewCount() == 1); + host->GetChildViewAt(0)->GetPreferredSize(out); +} + +/////////////////////////////////////////////////////////////////////////////// +// Window class tracking. + +// static +const wchar_t* const HWNDViewContainer::kBaseClassName = + L"Chrome_HWNDViewContainer_"; + +// Window class information used for registering unique windows. +struct ClassInfo { + UINT style; + HICON icon; + HICON small_icon; + HBRUSH background; + + explicit ClassInfo(int style) + : style(style), + icon(NULL), + small_icon(NULL), + background(NULL) {} + + // Compares two ClassInfos. Returns true if all members match. + bool Equals(const ClassInfo& other) { + return (other.style == style && other.icon == icon && + other.small_icon == icon && other.background == background); + } +}; + +// Represents a registered window class. +struct RegisteredClass { + RegisteredClass(const ClassInfo& info, + const std::wstring& name, + ATOM atom) + : info(info), + name(name), + atom(atom) { + } + + // Info used to create the class. + ClassInfo info; + + // The name given to the window. + std::wstring name; + + // The ATOM returned from creating the window. + ATOM atom; +}; + +typedef std::list<RegisteredClass> RegisteredClasses; + +// The list of registered classes. +static RegisteredClasses* registered_classes = NULL; + + +/////////////////////////////////////////////////////////////////////////////// +// HWNDViewContainer, public + +HWNDViewContainer::HWNDViewContainer() + : tracking_mouse_events_(false), + has_capture_(false), + current_action_(FA_NONE), + toplevel_(false), + window_style_(0), + window_ex_style_(kWindowDefaultExStyle), + layered_(false), + layered_alpha_(255), + delete_on_destroy_(true), + can_update_layered_window_(true), + last_mouse_event_was_move_(false), + is_mouse_down_(false), + class_style_(CS_DBLCLKS), + hwnd_(NULL), + close_container_factory_(this) { +} + +HWNDViewContainer::~HWNDViewContainer() { + MessageLoop::current()->RemoveObserver(this); +} + +void HWNDViewContainer::Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + bool has_own_focus_manager) { + toplevel_ = parent == NULL; + + if (window_style_ == 0) + window_style_ = toplevel_ ? kWindowDefaultStyle : kWindowDefaultChildStyle; + + // See if the style has been overridden. + opaque_ = !(window_ex_style_ & WS_EX_TRANSPARENT); + layered_ = !!(window_ex_style_ & WS_EX_LAYERED); + + // Force creation of the RootView if it hasn't been created yet. + GetRootView(); + + // Ensures the parent we have been passed is valid, otherwise CreateWindowEx + // will fail. + if (parent && !::IsWindow(parent)) { + NOTREACHED() << "invalid parent window specified."; + parent = NULL; + } + + hwnd_ = CreateWindowEx(window_ex_style_, GetWindowClassName().c_str(), L"", + window_style_, bounds.x(), bounds.y(), bounds.width(), + bounds.height(), parent, NULL, NULL, this); + DCHECK(hwnd_); + // The window procedure should have set the data for us. + DCHECK(win_util::GetWindowUserData(hwnd_) == this); + + root_view_->OnViewContainerCreated(); + + if (has_own_focus_manager) { + ChromeViews::FocusManager::CreateFocusManager(hwnd_, GetRootView()); + } else { + // Subclass the window so we get the tab key messages when a view with no + // associated native window is focused. + FocusManager::InstallFocusSubclass(hwnd_, NULL); + } + + // The RootView is set up _after_ the window is created so that its + // ViewContainer pointer is valid. + if (contents_view) { + // The FillLayout only applies when we have been provided with a single + // contents view. If the user intends to manage the RootView themselves, + // they are responsible for providing their own LayoutManager, since + // FillLayout is only capable of laying out a single child view. + root_view_->SetLayoutManager(new FillLayout()); + root_view_->AddChildView(contents_view); + } + + // Manually size the window here to ensure the root view is laid out. + ChangeSize(0, CSize(bounds.width(), bounds.height())); + + // Sets the RootView as a property, so the automation can introspect windows. + SetRootViewForHWND(hwnd_, root_view_.get()); + + MessageLoop::current()->AddObserver(this); + + // Windows special DWM window frame requires a special tooltip manager so + // that window controls in Chrome windows don't flicker when you move your + // mouse over them. See comment in aero_tooltip_manager.h. + if (win_util::ShouldUseVistaFrame()) { + tooltip_manager_.reset(new AeroTooltipManager(this, GetHWND())); + } else { + tooltip_manager_.reset(new TooltipManager(this, GetHWND())); + } + + // This message initializes the window so that focus border are shown for + // windows. + ::SendMessage(GetHWND(), + WM_CHANGEUISTATE, + MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS), + 0); + + // Bug 964884: detach the IME attached to this window. + // We should attach IMEs only when we need to input CJK strings. + ::ImmAssociateContextEx(GetHWND(), NULL, 0); +} + +/////////////////////////////////////////////////////////////////////////////// +// ChromeViews::ViewContainer + +void HWNDViewContainer::GetBounds(CRect *out, bool including_frame) const { + if (including_frame) { + GetWindowRect(out); + } else { + GetClientRect(out); + + POINT p = {0, 0}; + ::ClientToScreen(hwnd_, &p); + + out->left += p.x; + out->top += p.y; + out->right += p.x; + out->bottom += p.y; + } +} + +void HWNDViewContainer::MoveToFront(bool should_activate) { + int flags = SWP_NOMOVE | SWP_NOSIZE; + if (!should_activate) { + flags |= SWP_NOACTIVATE; + } + SetWindowPos(HWND_NOTOPMOST, 0, 0, 0, 0, flags); +} + +HWND HWNDViewContainer::GetHWND() const { + return hwnd_; +} + +void HWNDViewContainer::PaintNow(const CRect& update_rect) { + if (layered_) { + PaintLayeredWindow(); + } else if (root_view_->NeedsPainting(false) && IsWindow()) { + if (!opaque_ && GetParent()) { + // We're transparent. Need to force painting to occur from our parent. + CRect parent_update_rect = update_rect; + POINT location_in_parent = { 0, 0 }; + ClientToScreen(hwnd_, &location_in_parent); + ::ScreenToClient(GetParent(), &location_in_parent); + parent_update_rect.OffsetRect(location_in_parent); + ::RedrawWindow(GetParent(), parent_update_rect, NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + } else { + RedrawWindow(hwnd_, update_rect, NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + } + // As we were created with a style of WS_CLIPCHILDREN redraw requests may + // result in an empty paint rect in WM_PAINT (this'll happen if a + // child HWND completely contains the update _rect). In such a scenario + // RootView would never get a ProcessPaint and always think it needs to + // be painted (leading to a steady stream of RedrawWindow requests on every + // event). For this reason we tell RootView it doesn't need to paint + // here. + root_view_->ClearPaintRect(); + } +} + +RootView* HWNDViewContainer::GetRootView() { + if (!root_view_.get()) { + // First time the root view is being asked for, create it now. + root_view_.reset(CreateRootView()); + } + return root_view_.get(); +} + +bool HWNDViewContainer::IsVisible() { + return !!::IsWindowVisible(GetHWND()); +} + +bool HWNDViewContainer::IsActive() { + return win_util::IsWindowActive(GetHWND()); +} + +TooltipManager* HWNDViewContainer::GetTooltipManager() { + return tooltip_manager_.get(); +} + + +void HWNDViewContainer::SetLayeredAlpha(BYTE layered_alpha) { + layered_alpha_ = layered_alpha; + +// if (hwnd_) +// UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC()); +} + +static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM l_param) { + RootView* root_view = + reinterpret_cast<RootView*>(GetProp(hwnd, kRootViewWindowProperty)); + if (root_view) { + *reinterpret_cast<RootView**>(l_param) = root_view; + return FALSE; // Stop enumerating. + } + return TRUE; // Keep enumerating. +} + +// static +RootView* HWNDViewContainer::FindRootView(HWND hwnd) { + RootView* root_view = + reinterpret_cast<RootView*>(GetProp(hwnd, kRootViewWindowProperty)); + if (root_view) + return root_view; + + // Enumerate all children and check if they have a RootView. + EnumChildWindows(hwnd, EnumChildProc, reinterpret_cast<LPARAM>(&root_view)); + + return root_view; +} + +void HWNDViewContainer::Close() { + // Let's hide ourselves right away. + Hide(); + if (close_container_factory_.empty()) { + // And we delay the close so that if we are called from an ATL callback, + // we don't destroy the window before the callback returned (as the caller + // may delete ourselves on destroy and the ATL callback would still + // dereference us when the callback returns). + MessageLoop::current()->PostTask(FROM_HERE, + close_container_factory_.NewRunnableMethod( + &HWNDViewContainer::CloseNow)); + } +} + +void HWNDViewContainer::Hide() { + // NOTE: Be careful not to activate any windows here (for example, calling + // ShowWindow(SW_HIDE) will automatically activate another window). This + // code can be called while a window is being deactivated, and activating + // another window will screw up the activation that is already in progress. + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER); +} + +void HWNDViewContainer::CloseNow() { + // We may already have been destroyed if the selection resulted in a tab + // switch which will have reactivated the browser window and closed us, so + // we need to check to see if we're still a window before trying to destroy + // ourself. + if (IsWindow()) + DestroyWindow(); +} + +/////////////////////////////////////////////////////////////////////////////// +// MessageLoop::Observer + +void HWNDViewContainer::WillProcessMessage(const MSG& msg) { +} + +void HWNDViewContainer::DidProcessMessage(const MSG& msg) { + if (root_view_->NeedsPainting(true)) { + PaintNow(root_view_->GetScheduledPaintRect()); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// FocusTraversable + +View* HWNDViewContainer::FindNextFocusableView( + View* starting_view, bool reverse, Direction direction, bool dont_loop, + FocusTraversable** focus_traversable, View** focus_traversable_view) { + return root_view_->FindNextFocusableView(starting_view, + reverse, + direction, + dont_loop, + focus_traversable, + focus_traversable_view); +} + +FocusTraversable* HWNDViewContainer::GetFocusTraversableParent() { + // We are a proxy to the root view, so we should be bypassed when traversing + // up and as a result this should not be called. + NOTREACHED(); + return NULL; +} + +void HWNDViewContainer::SetFocusTraversableParent(FocusTraversable* parent) { + root_view_->SetFocusTraversableParent(parent); +} + +View* HWNDViewContainer::GetFocusTraversableParentView() { + // We are a proxy to the root view, so we should be bypassed when traversing + // up and as a result this should not be called. + NOTREACHED(); + return NULL; +} + +void HWNDViewContainer::SetFocusTraversableParentView(View* parent_view) { + root_view_->SetFocusTraversableParentView(parent_view); +} + +/////////////////////////////////////////////////////////////////////////////// +// Message handlers + +void HWNDViewContainer::OnCaptureChanged(HWND hwnd) { + if (has_capture_) { + if (is_mouse_down_) + root_view_->ProcessMouseDragCanceled(); + is_mouse_down_ = false; + has_capture_ = false; + } +} + +void HWNDViewContainer::OnClose() { + // WARNING: this method is NOT called for all HWNDViewContainers. If you + // need to do cleanup code before HWNDViewContainer is destroyed, put it + // in OnDestroy. + + NotificationService::current()->Notify( + NOTIFY_WINDOW_CLOSED, Source<HWND>(hwnd_), + NotificationService::NoDetails()); + + Close(); +} + +void HWNDViewContainer::OnDestroy() { + root_view_->OnViewContainerDestroyed(); + + RemoveProp(hwnd_, kRootViewWindowProperty); +} + +LRESULT HWNDViewContainer::OnEraseBkgnd(HDC dc) { + // This is needed for magical win32 flicker ju-ju + return 1; +} + +void HWNDViewContainer::OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags) { + KeyEvent event(Event::ET_KEY_PRESSED, c, rep_cnt, flags); + root_view_->ProcessKeyEvent(event); +} + +void HWNDViewContainer::OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags) { + KeyEvent event(Event::ET_KEY_RELEASED, c, rep_cnt, flags); + root_view_->ProcessKeyEvent(event); +} + +void HWNDViewContainer::OnLButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_LBUTTON, false); +} + +void HWNDViewContainer::OnLButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_LBUTTON); +} + +void HWNDViewContainer::OnLButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_LBUTTON, true); +} + +void HWNDViewContainer::OnMButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_MBUTTON, false); +} + +void HWNDViewContainer::OnMButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_MBUTTON); +} + +void HWNDViewContainer::OnMButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_MBUTTON, true); +} + +LRESULT HWNDViewContainer::OnMouseActivate(HWND window, + UINT hittest_code, + UINT message) { + SetMsgHandled(FALSE); + return MA_ACTIVATE; +} + +void HWNDViewContainer::OnMouseMove(UINT flags, const CPoint& point) { + ProcessMouseMoved(point, flags); +} + +void HWNDViewContainer::OnMouseLeave() { + ProcessMouseExited(); +} + +LRESULT HWNDViewContainer::OnMouseWheel(UINT flags, + short distance, + const CPoint& point) { + MouseWheelEvent e(distance, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + return root_view_->ProcessMouseWheelEvent(e) ? 0 : 1; +} + +LRESULT HWNDViewContainer::OnMouseRange(UINT msg, + WPARAM w_param, + LPARAM l_param) { + tooltip_manager_->OnMouse(msg, w_param, l_param); + SetMsgHandled(FALSE); + return 0; +} + +void HWNDViewContainer::OnNCLButtonDblClk(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +void HWNDViewContainer::OnNCLButtonDown(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +void HWNDViewContainer::OnNCLButtonUp(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +LRESULT HWNDViewContainer::OnNCMouseMove(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); + return 0; +} + +void HWNDViewContainer::OnNCRButtonDblClk(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +void HWNDViewContainer::OnNCRButtonDown(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +void HWNDViewContainer::OnNCRButtonUp(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +LRESULT HWNDViewContainer::OnNotify(int w_param, NMHDR* l_param) { + // We can be sent this message before the tooltip manager is created, if a + // subclass overrides OnCreate and creates some kind of Windows control there + // that sends WM_NOTIFY messages. + if (tooltip_manager_.get()) { + bool handled; + LRESULT result = tooltip_manager_->OnNotify(w_param, l_param, &handled); + SetMsgHandled(handled); + return result; + } + SetMsgHandled(FALSE); + return 0; +} + +void HWNDViewContainer::OnPaint(HDC dc) { + root_view_->OnPaint(GetHWND()); +} + +void HWNDViewContainer::OnRButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_RBUTTON, false); +} + +void HWNDViewContainer::OnRButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_RBUTTON); +} + +void HWNDViewContainer::OnRButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_RBUTTON, true); +} + +LRESULT HWNDViewContainer::OnSettingChange(UINT msg, + WPARAM w_param, + LPARAM l_param) { + if (toplevel_) { + SetMsgHandled(FALSE); + if (w_param != SPI_SETWORKAREA) + return 0; // Return value is effectively ignored in atlwin.h. + + AdjustWindowToFitScreenSize(); + SetMsgHandled(TRUE); + } + // Don't care, overridden by interested subclasses + return 0; +} + +void HWNDViewContainer::OnSize(UINT param, const CSize& size) { + ChangeSize(param, size); +} + +void HWNDViewContainer::OnFinalMessage(HWND window) { + if (delete_on_destroy_) + delete this; +} + +/////////////////////////////////////////////////////////////////////////////// +// HWNDViewContainer, protected + +void HWNDViewContainer::TrackMouseEvents() { + // Begin tracking mouse events for this HWND so that we get WM_MOUSELEAVE + // when the user moves the mouse outside this HWND's bounds. + if (!tracking_mouse_events_) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = GetHWND(); + tme.dwHoverTime = 0; + TrackMouseEvent(&tme); + tracking_mouse_events_ = true; + } +} + +bool HWNDViewContainer::ProcessMousePressed(const CPoint& point, + UINT flags, + bool dbl_click) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_pressed(Event::ET_MOUSE_PRESSED, + point.x, + point.y, + (dbl_click ? MouseEvent::EF_IS_DOUBLE_CLICK : 0) | + Event::ConvertWindowsFlags(flags)); + if (root_view_->OnMousePressed(mouse_pressed)) { + is_mouse_down_ = true; + if (!has_capture_) { + SetCapture(); + has_capture_ = true; + current_action_ = FA_FORWARDING; + } + return true; + } + return false; +} + +void HWNDViewContainer::ProcessMouseDragged(const CPoint& point, UINT flags) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_drag(Event::ET_MOUSE_DRAGGED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + root_view_->OnMouseDragged(mouse_drag); +} + +void HWNDViewContainer::ProcessMouseReleased(const CPoint& point, UINT flags) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_up(Event::ET_MOUSE_RELEASED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + // Release the capture first, that way we don't get confused if + // OnMouseReleased blocks. + if (has_capture_ && ReleaseCaptureOnMouseReleased()) { + has_capture_ = false; + current_action_ = FA_NONE; + ReleaseCapture(); + } + is_mouse_down_ = false; + root_view_->OnMouseReleased(mouse_up, false); +} + +void HWNDViewContainer::ProcessMouseMoved(const CPoint &point, UINT flags) { + // Windows only fires WM_MOUSELEAVE events if the application begins + // "tracking" mouse events for a given HWND during WM_MOUSEMOVE events. + // We need to call |TrackMouseEvents| to listen for WM_MOUSELEAVE. + if (!has_capture_) + TrackMouseEvents(); + if (has_capture_ && is_mouse_down_) { + ProcessMouseDragged(point, flags); + } else { + CPoint screen_loc = point; + View::ConvertPointToScreen(root_view_.get(), &screen_loc); + if (last_mouse_event_was_move_ && last_mouse_move_x_ == screen_loc.x && + last_mouse_move_y_ == screen_loc.y) { + // Don't generate a mouse event for the same location as the last. + return; + } + last_mouse_move_x_ = screen_loc.x; + last_mouse_move_y_ = screen_loc.y; + last_mouse_event_was_move_ = true; + MouseEvent mouse_move(Event::ET_MOUSE_MOVED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + root_view_->OnMouseMoved(mouse_move); + } +} + +void HWNDViewContainer::ProcessMouseExited() { + last_mouse_event_was_move_ = false; + root_view_->ProcessOnMouseExited(); + // Reset our tracking flag so that future mouse movement over this + // HWNDViewContainer results in a new tracking session. + tracking_mouse_events_ = false; +} + +void HWNDViewContainer::AdjustWindowToFitScreenSize() { + // Desktop size has changed. Make sure we're still on screen. + CRect wr; + GetWindowRect(&wr); + HMONITOR hmon = MonitorFromRect(&wr, MONITOR_DEFAULTTONEAREST); + if (!hmon) { + // No monitor available. + return; + } + + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(hmon, &mi); + gfx::Rect window_rect(wr); + gfx::Rect monitor_rect(mi.rcWork); + gfx::Rect new_window_rect = window_rect.AdjustToFit(monitor_rect); + if (!new_window_rect.Equals(window_rect)) { + // New position differs from last, resize window. + ::SetWindowPos(GetHWND(), + 0, + new_window_rect.x(), + new_window_rect.y(), + new_window_rect.width(), + new_window_rect.height(), + SWP_NOACTIVATE | SWP_NOZORDER); + } +} + +void HWNDViewContainer::ChangeSize(UINT size_param, const CSize& size) { + CRect rect; + if (layered_) { + GetWindowRect(&rect); + SizeContents(rect); + } else { + GetClientRect(&rect); + } + + // Resizing changes the size of the view hierarchy and thus forces a + // complete relayout. + root_view_->SetBounds(CRect(CPoint(0,0), rect.Size())); + root_view_->Layout(); + root_view_->SchedulePaint(); + + if (layered_) + PaintNow(rect); +} + +RootView* HWNDViewContainer::CreateRootView() { + return new RootView(this, true); +} + +/////////////////////////////////////////////////////////////////////////////// +// HWNDViewContainer, private: + +void HWNDViewContainer::SizeContents(const CRect& window_rect) { + contents_.reset(new ChromeCanvas(window_rect.Width(), + window_rect.Height(), + false)); +} + +void HWNDViewContainer::PaintLayeredWindow() { + // Painting monkeys with our cliprect, so we need to save it so that the + // call to UpdateLayeredWindow updates the entire window, not just the + // cliprect. + contents_->save(SkCanvas::kClip_SaveFlag); + CRect dirty_rect = root_view_->GetScheduledPaintRect(); + contents_->ClipRectInt( + dirty_rect.left, dirty_rect.top, dirty_rect.Width(), dirty_rect.Height()); + root_view_->ProcessPaint(contents_.get()); + contents_->restore(); + + UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC()); +} + +void HWNDViewContainer::UpdateWindowFromContents(HDC dib_dc) { + DCHECK(layered_); + if (can_update_layered_window_) { + CRect wr; + GetWindowRect(&wr); + CSize size(wr.right - wr.left, wr.bottom - wr.top); + CPoint zero_origin(0, 0); + CPoint window_position = wr.TopLeft(); + + BLENDFUNCTION blend = {AC_SRC_OVER, 0, layered_alpha_, AC_SRC_ALPHA}; + ::UpdateLayeredWindow( + hwnd_, NULL, &window_position, &size, dib_dc, &zero_origin, + RGB(0xFF, 0xFF, 0xFF), &blend, ULW_ALPHA); + } +} + +std::wstring HWNDViewContainer::GetWindowClassName() { + if (!registered_classes) + registered_classes = new RegisteredClasses(); + ClassInfo class_info(initial_class_style()); + for (RegisteredClasses::iterator i = registered_classes->begin(); + i != registered_classes->end(); ++i) { + if (class_info.Equals(i->info)) + return i->name; + } + + // No class found, need to register one. + static int registered_count = 0; + std::wstring name = + std::wstring(kBaseClassName) + IntToWString(registered_count++); + WNDCLASSEX class_ex; + class_ex.cbSize = sizeof(WNDCLASSEX); + class_ex.style = class_info.style; + class_ex.lpfnWndProc = &HWNDViewContainer::WndProc; + class_ex.cbClsExtra = 0; + class_ex.cbWndExtra = 0; + class_ex.hInstance = NULL; + class_ex.hIcon = class_info.icon; + class_ex.hCursor = LoadCursor(NULL, IDC_ARROW); + class_ex.hbrBackground = reinterpret_cast<HBRUSH>(class_info.background + 1); + class_ex.lpszMenuName = NULL; + class_ex.lpszClassName = name.c_str(); + class_ex.hIconSm = class_info.small_icon; + ATOM atom = RegisterClassEx(&class_ex); + DCHECK(atom); + RegisteredClass registered_class(class_info, name, atom); + registered_classes->push_back(registered_class); + return name; +} + +// static +LRESULT CALLBACK HWNDViewContainer::WndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param) { + if (message == WM_NCCREATE) { + CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(l_param); + HWNDViewContainer* vc = + reinterpret_cast<HWNDViewContainer*>(cs->lpCreateParams); + DCHECK(vc); + win_util::SetWindowUserData(window, vc); + vc->hwnd_ = window; + return TRUE; + } + HWNDViewContainer* vc = reinterpret_cast<HWNDViewContainer*>( + win_util::GetWindowUserData(window)); + if (!vc) + return 0; + LRESULT result = 0; + if (!vc->ProcessWindowMessage(window, message, w_param, l_param, result)) + result = DefWindowProc(window, message, w_param, l_param); + if (message == WM_NCDESTROY) { + vc->hwnd_ = NULL; + vc->OnFinalMessage(window); + } + return result; +} + +} // namespace ChromeViews diff --git a/chrome/views/hwnd_view_container.h b/chrome/views/hwnd_view_container.h new file mode 100644 index 0000000..a8fd689 --- /dev/null +++ b/chrome/views/hwnd_view_container.h @@ -0,0 +1,525 @@ +// 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. + +#ifndef CHROME_VIEWS_HWND_VIEW_CONTAINER_H__ +#define CHROME_VIEWS_HWND_VIEW_CONTAINER_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> + +#include "base/message_loop.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/layout_manager.h" +#include "chrome/views/root_view.h" +#include "chrome/views/view_container.h" + +namespace ChromeViews { + +class TooltipManager; + +bool SetRootViewForHWND(HWND hwnd, RootView* root_view); +RootView* GetRootViewForHWND(HWND hwnd); + +// A Windows message reflected from other windows. This message is sent +// with the following arguments: +// hWnd - Target window +// uMsg - kReflectedMessage +// wParam - Should be 0 +// lParam - Pointer to MSG struct containing the original message. +static const int kReflectedMessage = WM_APP + 3; + +/////////////////////////////////////////////////////////////////////////////// +// +// FillLayout +// A simple LayoutManager that causes the associated view's one child to be +// sized to match the bounds of its parent. +// +/////////////////////////////////////////////////////////////////////////////// +class FillLayout : public LayoutManager { + public: + FillLayout(); + virtual ~FillLayout(); + + // Overridden from LayoutManager: + virtual void Layout(View* host); + virtual void GetPreferredSize(View* host, CSize* out); + + private: + DISALLOW_EVIL_CONSTRUCTORS(FillLayout); +}; + +/////////////////////////////////////////////////////////////////////////////// +// +// HWNDViewContainer +// A container for a ChromeViews hierarchy used to represent anything that can +// be contained within an HWND, e.g. a control, a window, etc. Specializations +// suitable for specific tasks, e.g. top level window, are derived from this. +// +// This ViewContainer contains a RootView which owns the hierarchy of +// ChromeViews within it. As long as ChromeViews are part of this tree, they +// will be deleted automatically when the RootView is destroyed. If you remove +// a view from the tree, you are then responsible for cleaning up after it. +// +// Note: We try and keep this API platform-neutral, since to some extent we +// consider this the boundary between the platform and potentially cross +// platform ChromeViews code. In some cases this isn't true, primarily +// because of other code that has not yet been refactored to maintain +// this <controller>-<view>-<platform-specific-view> separation. +// +/////////////////////////////////////////////////////////////////////////////// +class HWNDViewContainer : public ViewContainer, + public MessageLoop::Observer, + public FocusTraversable { + public: + HWNDViewContainer(); + virtual ~HWNDViewContainer(); + + // Initialize the container with a parent and an initial desired size. + // |contents_view| is the view that will be the single child of RootView + // within this ViewContainer. As contents_view is inserted into RootView's + // tree, RootView assumes ownership of this view and cleaning it up. If you + // remove this view, you are responsible for its destruction. If this value + // is NULL, the caller is responsible for populating the RootView, and sizing + // its contents as the window is sized. + // If |has_own_focus_manager| is true, the focus traversal stay confined to + // the window. + void Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + bool has_own_focus_manager); + + // Sets the window styles. This is ONLY used when the window is created. + // In other words, if you invoke this after invoking Init, nothing happens. + void set_window_style(DWORD style) { window_style_ = style; } + DWORD window_style() const { return window_style_; } + + // Sets the extended window styles. See comment about |set_window_style|. + void set_window_ex_style(DWORD style) { window_ex_style_ = style; } + DWORD window_ex_style() const { return window_ex_style_; }; + + // Sets the class style to use. The default is CS_DBLCLKS. + void set_initial_class_style(UINT class_style) { + // We dynamically generate the class name, so don't register it globally! + DCHECK((class_style & CS_GLOBALCLASS) == 0); + class_style_ = class_style; + } + UINT initial_class_style() { return class_style_; } + + void set_delete_on_destroy(bool delete_on_destroy) { + delete_on_destroy_ = delete_on_destroy; + } + + // Sets the initial opacity of a layered window, or updates the window's + // opacity if it is on the screen. + void SetLayeredAlpha(BYTE layered_alpha); + + // Disable Layered Window updates by setting to false. + void set_can_update_layered_window(bool can_update_layered_window) { + can_update_layered_window_ = can_update_layered_window; + } + + // Returns the RootView associated with the specified HWND (if any). + static RootView* FindRootView(HWND hwnd); + + // Closes the window asynchronously by scheduling a task for it. The window + // is destroyed as a result. + // This invokes Hide to hide the window, and schedules a task that + // invokes CloseNow. + virtual void Close(); + + // Hides the window. This does NOT delete the window, it just hides it. + virtual void Hide(); + + // Closes the window synchronously. Note that this should not be called from + // an ATL message callback as it deletes the HWNDDViewContainer and ATL will + // dereference it after the callback is processed. + void CloseNow(); + + // All classes registered by HWNDViewContainer start with this name. + static const wchar_t* const kBaseClassName; + + BEGIN_MSG_MAP_EX(0) + // Range handlers must go first! + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) + MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, WM_NCMOUSEMOVE, OnMouseRange) + MESSAGE_RANGE_HANDLER_EX(WM_SETTINGCHANGE, WM_SETTINGCHANGE, OnSettingChange) + + // Reflected message handler + MESSAGE_HANDLER_EX(kReflectedMessage, OnReflectedMessage) + + // This list is in _ALPHABETICAL_ order! OR I WILL HURT YOU. + MSG_WM_ACTIVATE(OnActivate) + MSG_WM_CANCELMODE(OnCancelMode) + MSG_WM_CAPTURECHANGED(OnCaptureChanged) + MSG_WM_CLOSE(OnClose) + MSG_WM_COMMAND(OnCommand) + MSG_WM_CREATE(OnCreate) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_ERASEBKGND(OnEraseBkgnd) + MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo) + MSG_WM_HSCROLL(OnHScroll) + MSG_WM_INITMENU(OnInitMenu) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_KEYUP(OnKeyUp) + MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_LBUTTONUP(OnLButtonUp) + MSG_WM_MBUTTONDOWN(OnMButtonDown) + MSG_WM_MBUTTONUP(OnMButtonUp) + MSG_WM_MBUTTONDBLCLK(OnMButtonDblClk) + MSG_WM_MOUSEACTIVATE(OnMouseActivate) + MSG_WM_MOUSELEAVE(OnMouseLeave) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_MOUSEWHEEL(OnMouseWheel) + MSG_WM_MOVING(OnMoving) + MSG_WM_NCACTIVATE(OnNCActivate) + MSG_WM_NCCALCSIZE(OnNCCalcSize) + MSG_WM_NCHITTEST(OnNCHitTest) + MSG_WM_NCMOUSEMOVE(OnNCMouseMove) + MSG_WM_NCLBUTTONDBLCLK(OnNCLButtonDblClk) + MSG_WM_NCLBUTTONDOWN(OnNCLButtonDown) + MSG_WM_NCLBUTTONUP(OnNCLButtonUp) + MSG_WM_NCPAINT(OnNCPaint) + MSG_WM_NCRBUTTONDBLCLK(OnNCRButtonDblClk) + MSG_WM_NCRBUTTONDOWN(OnNCRButtonDown) + MSG_WM_NCRBUTTONUP(OnNCRButtonUp) + MSG_WM_NOTIFY(OnNotify) + MSG_WM_PAINT(OnPaint) + MSG_WM_RBUTTONDBLCLK(OnRButtonDblClk) + MSG_WM_RBUTTONDOWN(OnRButtonDown) + MSG_WM_RBUTTONUP(OnRButtonUp) + MSG_WM_SETCURSOR(OnSetCursor) + MSG_WM_SETFOCUS(OnSetFocus) + MSG_WM_SIZE(OnSize) + MSG_WM_SYSCOMMAND(OnSysCommand) + MSG_WM_VSCROLL(OnVScroll) + MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged) + END_MSG_MAP() + + // Overridden from ViewContainer: + virtual void GetBounds(CRect *out, bool including_frame) const; + virtual void MoveToFront(bool should_activate); + virtual HWND GetHWND() const; + virtual void PaintNow(const CRect& update_rect); + virtual RootView* GetRootView(); + virtual bool IsVisible(); + virtual bool IsActive(); + virtual TooltipManager* GetTooltipManager(); + + // Overridden from MessageLoop::Observer: + void WillProcessMessage(const MSG& msg); + virtual void DidProcessMessage(const MSG& msg); + + // Overridden from FocusTraversable: + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + virtual FocusTraversable* GetFocusTraversableParent(); + virtual View* GetFocusTraversableParentView(); + + void SetFocusTraversableParent(FocusTraversable* parent); + void SetFocusTraversableParentView(View* parent_view); + + virtual bool GetAccelerator(int cmd_id, + ChromeViews::Accelerator* accelerator) { + return false; + } + + BOOL IsWindow() const { + return ::IsWindow(GetHWND()); + } + + BOOL ShowWindow(int command) { + DCHECK(::IsWindow(GetHWND())); + return ::ShowWindow(GetHWND(), command); + } + + HWND SetCapture() { + DCHECK(::IsWindow(GetHWND())); + return ::SetCapture(GetHWND()); + } + + HWND GetParent() const { + return ::GetParent(GetHWND()); + } + + BOOL GetWindowRect(RECT* rect) const { + return ::GetWindowRect(GetHWND(), rect); + } + + BOOL SetWindowPos(HWND hwnd_after, int x, int y, int cx, int cy, UINT flags) { + DCHECK(::IsWindow(GetHWND())); + return ::SetWindowPos(GetHWND(), hwnd_after, x, y, cx, cy, flags); + } + + BOOL IsZoomed() const { + DCHECK(::IsWindow(GetHWND())); + return ::IsZoomed(GetHWND()); + } + + BOOL MoveWindow(int x, int y, int width, int height) { + return MoveWindow(x, y, width, height, TRUE); + } + + BOOL MoveWindow(int x, int y, int width, int height, BOOL repaint) { + DCHECK(::IsWindow(GetHWND())); + return ::MoveWindow(GetHWND(), x, y, width, height, repaint); + } + + int SetWindowRgn(HRGN region, BOOL redraw) { + DCHECK(::IsWindow(GetHWND())); + return ::SetWindowRgn(GetHWND(), region, redraw); + } + + BOOL GetClientRect(RECT* rect) const { + DCHECK(::IsWindow(GetHWND())); + return ::GetClientRect(GetHWND(), rect); + } + + protected: + + // Call close instead of this to Destroy the window. + BOOL DestroyWindow() { + DCHECK(::IsWindow(GetHWND())); + return ::DestroyWindow(GetHWND()); + } + + // Message Handlers + // These are all virtual so that specialized view containers can modify or + // augment processing. + // This list is in _ALPHABETICAL_ order! + // Note: in the base class these functions must do nothing but convert point + // coordinates to client coordinates (if necessary) and forward the + // handling to the appropriate Process* function. This is so that + // subclasses can easily override these methods to do different things + // and have a convenient function to call to get the default behavior. + virtual void OnActivate(UINT action, BOOL minimized, HWND window) { } + virtual void OnCancelMode() {} + virtual void OnCaptureChanged(HWND hwnd); + virtual void OnClose(); + virtual void OnCommand( + UINT notification_code, int command_id, HWND window) { } + virtual LRESULT OnCreate(LPCREATESTRUCT create_struct) { return 0; } + // WARNING: If you override this be sure and invoke super, otherwise we'll + // leak a few things. + virtual void OnDestroy(); + virtual LRESULT OnEraseBkgnd(HDC dc); + virtual void OnGetMinMaxInfo(LPMINMAXINFO mm_info) { } + virtual void OnHScroll(int scroll_type, short position, HWND scrollbar) { + SetMsgHandled(FALSE); + } + virtual void OnInitMenu(HMENU menu) { SetMsgHandled(FALSE); } + virtual void OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags); + virtual void OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags); + virtual void OnLButtonDblClk(UINT flags, const CPoint& point); + virtual void OnLButtonDown(UINT flags, const CPoint& point); + virtual void OnLButtonUp(UINT flags, const CPoint& point); + virtual void OnMButtonDblClk(UINT flags, const CPoint& point); + virtual void OnMButtonDown(UINT flags, const CPoint& point); + virtual void OnMButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnMouseActivate(HWND window, UINT hittest_code, UINT message); + virtual void OnMouseMove(UINT flags, const CPoint& point); + virtual void OnMouseLeave(); + virtual void OnMoving(UINT param, const LPRECT new_bounds) { } + virtual LRESULT OnMouseWheel(UINT flags, short distance, const CPoint& point); + virtual LRESULT OnMouseRange(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnNCActivate(BOOL active) { SetMsgHandled(FALSE); return 0; } + virtual LRESULT OnNCCalcSize(BOOL w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } + virtual LRESULT OnNCHitTest(const CPoint& pt) { SetMsgHandled(FALSE); return 0; } + virtual void OnNCLButtonDblClk(UINT flags, const CPoint& point); + virtual void OnNCLButtonDown(UINT flags, const CPoint& point); + virtual void OnNCLButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnNCMouseMove(UINT flags, const CPoint& point); + virtual void OnNCPaint(HRGN rgn) { SetMsgHandled(FALSE); } + virtual void OnNCRButtonDblClk(UINT flags, const CPoint& point); + virtual void OnNCRButtonDown(UINT flags, const CPoint& point); + virtual void OnNCRButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnNotify(int w_param, NMHDR* l_param); + virtual void OnPaint(HDC dc); + virtual void OnRButtonDblClk(UINT flags, const CPoint& point); + virtual void OnRButtonDown(UINT flags, const CPoint& point); + virtual void OnRButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnReflectedMessage(UINT msg, WPARAM w_param, LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnSetFocus(HWND focused_window) { + SetMsgHandled(FALSE); + } + virtual LRESULT OnSettingChange(UINT msg, WPARAM w_param, LPARAM l_param); + virtual void OnSize(UINT param, const CSize& size); + virtual void OnSysCommand(UINT notification_code, CPoint click) { } + virtual void OnVScroll(int scroll_type, short position, HWND scrollbar) { + SetMsgHandled(FALSE); + } + virtual void OnWindowPosChanged(WINDOWPOS* window_pos) { + SetMsgHandled(FALSE); + } + + // deletes this window as it is destroyed, override to provide different + // behavior. + virtual void OnFinalMessage(HWND window); + + // Start tracking all mouse events so that this window gets sent mouse leave + // messages too. + void TrackMouseEvents(); + + // Actually handle mouse events. These functions are called by subclasses who + // override the message handlers above to do the actual real work of handling + // the event in the View system. + bool ProcessMousePressed(const CPoint& point, UINT flags, bool dbl_click); + void ProcessMouseDragged(const CPoint& point, UINT flags); + void ProcessMouseReleased(const CPoint& point, UINT flags); + void ProcessMouseMoved(const CPoint& point, UINT flags); + void ProcessMouseExited(); + + // Makes sure the window still fits on screen after a settings change message + // from the OS, e.g. a screen resolution change. + virtual void AdjustWindowToFitScreenSize(); + + // Handles re-laying out content in response to a window size change. + virtual void ChangeSize(UINT size_param, const CSize& size); + + // Returns whether capture should be released on mouse release. The default + // is true. + virtual bool ReleaseCaptureOnMouseReleased() { return true; } + + enum FrameAction {FA_NONE = 0, FA_RESIZING, FA_MOVING, FA_FORWARDING}; + + virtual RootView* CreateRootView(); + + // Returns true if this HWNDViewContainer is opaque. + bool opaque() const { return opaque_; } + + // The root of the View hierarchy attached to this window. + scoped_ptr<RootView> root_view_; + + // Current frame ui action + FrameAction current_action_; + + // Whether or not we have capture the mouse. + bool has_capture_; + + // If true, the mouse is currently down. + bool is_mouse_down_; + + scoped_ptr<TooltipManager> tooltip_manager_; + + private: + // Resize the bitmap used to contain the contents of the layered window. This + // recreates the entire bitmap. + void SizeContents(const CRect& window_rect); + + // Paint into a DIB and then update the layered window with its contents. + void PaintLayeredWindow(); + + // In layered mode, update the layered window. |dib_dc| represents a handle + // to a device context that contains the contents of the window. + void UpdateWindowFromContents(HDC dib_dc); + + // Invoked from WM_DESTROY. Does appropriate cleanup and invokes OnDestroy + // so that subclasses can do any cleanup they need to. + void OnDestroyImpl(); + + // The windows procedure used by all HWNDViewContainers. + static LRESULT CALLBACK WndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // Gets the window class name to use when creating the corresponding HWND. + // If necessary, this registers the window class. + std::wstring GetWindowClassName(); + + // The following factory is used for calls to close the HWNDViewContainer + // instance. + ScopedRunnableMethodFactory<HWNDViewContainer> close_container_factory_; + + // Whether or not we are currently tracking mouse events for this HWND + // using |::TrackMouseEvent| + bool tracking_mouse_events_; + + // Whether or not this is a top level window. + bool toplevel_; + + bool opaque_; + + // Window Styles used when creating the window. + DWORD window_style_; + + // Window Extended Styles used when creating the window. + DWORD window_ex_style_; + + // Style of the class to use. + UINT class_style_; + + // Whether or not this is a layered window. + bool layered_; + + // The default alpha to be applied to the layered window. + BYTE layered_alpha_; + + // A canvas that contains the window contents in the case of a layered + // window. + scoped_ptr<ChromeCanvas> contents_; + + // Whether or not the window should delete itself when it is destroyed. + // Set this to false via its setter for stack allocated instances. + bool delete_on_destroy_; + + // True if we are allowed to update the layered window from the DIB backing + // store if necessary. + bool can_update_layered_window_; + + // The following are used to detect duplicate mouse move events and not + // deliver them. Displaying a window may result in the system generating + // duplicate move events even though the mouse hasn't moved. + + // If true, the last event was a mouse move event. + bool last_mouse_event_was_move_; + + // Coordinates of the last mouse move event, in terms of the screen. + int last_mouse_move_x_; + int last_mouse_move_y_; + + // Our hwnd. + HWND hwnd_; +}; + +} + +#endif // #ifndef CHROME_VIEWS_HWND_VIEW_CONTAINER_H__ diff --git a/chrome/views/image_view.cc b/chrome/views/image_view.cc new file mode 100644 index 0000000..9d25651 --- /dev/null +++ b/chrome/views/image_view.cc @@ -0,0 +1,194 @@ +// 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 "base/logging.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/image_view.h" + +namespace ChromeViews { + +ImageView::ImageView() + : image_size_set_(false), + horiz_alignment_(CENTER), + vert_alignment_(CENTER) { +} + +ImageView::~ImageView() { +} + +void ImageView::SetImage(const SkBitmap& bm) { + image_ = bm; + SchedulePaint(); +} + +void ImageView::SetImage(SkBitmap* bm) { + if (bm) { + SetImage(*bm); + } else { + SkBitmap t; + SetImage(t); + } +} + +const SkBitmap& ImageView::GetImage() { + return image_; +} + +void ImageView::SetImageSize(const CSize& image_size) { + image_size_set_ = true; + image_size_ = image_size; +} + +bool ImageView::GetImageSize(CSize* image_size) { + DCHECK(image_size); + if (image_size_set_) + *image_size = image_size_; + return image_size_set_; +} + +void ImageView::ResetImageSize() { + image_size_set_ = false; +} + +void ImageView::GetPreferredSize(CSize* out) { + DCHECK(out); + if (image_size_set_) { + GetImageSize(out); + } else { + out->cx = image_.width(); + out->cy = image_.height(); + } +} + +void ImageView::ComputeImageOrigin(int image_width, int image_height, + int *x, int *y) { + // In order to properly handle alignment of images in RTL locales, we need + // to flip the meaning of trailing and leading. For example, if the + // horizontal alignment is set to trailing, then we'll use left alignment for + // the image instead of right alignment if the UI layout is RTL. + Alignment actual_horiz_alignment = horiz_alignment_; + if (UILayoutIsRightToLeft()) { + if (horiz_alignment_ == TRAILING) + actual_horiz_alignment = LEADING; + if (horiz_alignment_ == LEADING) + actual_horiz_alignment = TRAILING; + } + + switch(actual_horiz_alignment) { + case LEADING: + *x = 0; + break; + case TRAILING: + *x = GetWidth() - image_width; + break; + case CENTER: + *x = (GetWidth() - image_width) / 2; + break; + default: + NOTREACHED(); + } + + switch (vert_alignment_) { + case LEADING: + *y = 0; + break; + case TRAILING: + *y = GetHeight() - image_height; + break; + case CENTER: + *y = (GetHeight() - image_height) / 2; + break; + default: + NOTREACHED(); + } +} + +void ImageView::Paint(ChromeCanvas* canvas) { + View::Paint(canvas); + int image_width = image_.width(); + int image_height = image_.height(); + + if (image_width == 0 || image_height == 0) + return; + + int x, y; + if (image_size_set_ && + (image_size_.cx != image_width || image_size_.cy != image_height)) { + // Resize case + image_.buildMipMap(false); + ComputeImageOrigin(image_size_.cx, image_size_.cy, &x, &y); + canvas->DrawBitmapInt(image_, 0, 0, image_width, image_height, + x, y, image_size_.cx, image_size_.cy, + true); + } else { + ComputeImageOrigin(image_width, image_height, &x, &y); + canvas->DrawBitmapInt(image_, x, y); + } +} + +void ImageView::SetHorizontalAlignment(Alignment ha) { + if (ha != horiz_alignment_) { + horiz_alignment_ = ha; + SchedulePaint(); + } +} + +ImageView::Alignment ImageView::GetHorizontalAlignment() { + return horiz_alignment_; +} + +void ImageView::SetVerticalAlignment(Alignment va) { + if (va != vert_alignment_) { + vert_alignment_ = va; + SchedulePaint(); + } +} + +ImageView::Alignment ImageView::GetVerticalAlignment() { + return vert_alignment_; +} + +void ImageView::SetTooltipText(const std::wstring& tooltip) { + tooltip_text_ = tooltip; +} + +std::wstring ImageView::GetTooltipText() { + return tooltip_text_; +} + +bool ImageView::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (tooltip_text_.empty()) { + return false; + } else { + * tooltip = GetTooltipText(); + return true; + } +} + +} diff --git a/chrome/views/image_view.h b/chrome/views/image_view.h new file mode 100644 index 0000000..b56519d --- /dev/null +++ b/chrome/views/image_view.h @@ -0,0 +1,134 @@ +// 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. + +#ifndef CHROME_VIEWS_IMAGE_VIEW_H__ +#define CHROME_VIEWS_IMAGE_VIEW_H__ + +#include <windows.h> + +#include "chrome/views/view.h" +#include "SkBitmap.h" + +class ChromeCanvas; + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// ImageView class. +// +// An ImageView can display an image from an SkBitmap. If a size is provided, +// the ImageView will resize the provided image to fit if it is too big or will +// center the image if smaller. Otherwise, the preferred size matches the +// provided image size. +// +///////////////////////////////////////////////////////////////////////////// +class ImageView : public View { + public: + enum Alignment { + LEADING = 0, + CENTER, + TRAILING + }; + + ImageView(); + virtual ~ImageView(); + + // Set the bitmap that should be displayed. + void SetImage(const SkBitmap& bm); + + // Set the bitmap that should be displayed from a pointer. Reset the image + // if the pointer is NULL. The pointer contents is copied in the receiver's + // bitmap. + void SetImage(SkBitmap* bm); + + // Returns the bitmap currently displayed or NULL of none is currently set. + // The returned bitmap is still owned by the ImageView. + const SkBitmap& GetImage(); + + // Set the desired image size for the receiving ImageView. + void SetImageSize(const CSize& image_size); + + // Return the preferred size for the receiving view. Returns false if the + // preferred size is not defined, which means that the view uses the image + // size. + bool GetImageSize(CSize* image_size); + + // Reset the image size to the current image dimensions. + void ResetImageSize(); + + // Set / Get the horizontal alignment. + void SetHorizontalAlignment(Alignment ha); + Alignment GetHorizontalAlignment(); + + // Set / Get the vertical alignment. + void SetVerticalAlignment(Alignment va); + Alignment GetVerticalAlignment(); + + // Set / Get the tooltip text. + void SetTooltipText(const std::wstring& tooltip); + std::wstring GetTooltipText(); + + // Return whether the image should be centered inside the view. + // Overriden from View + virtual void GetPreferredSize(CSize* out); + virtual void Paint(ChromeCanvas* canvas); + + // Overriden from View. + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + private: + // Compute the image origin given the desired size and the receiver alignment + // properties. + void ComputeImageOrigin(int image_width, int image_height, int *x, int *y); + + // Whether the image size is set. + bool image_size_set_; + + // The actual image size. + CSize image_size_; + + // The underlying bitmap. + SkBitmap image_; + + // Horizontal alignment. + Alignment horiz_alignment_; + + // Vertical alignment. + Alignment vert_alignment_; + + // The current tooltip text. + std::wstring tooltip_text_; + + DISALLOW_EVIL_CONSTRUCTORS(ImageView); +}; + +} + +#endif // CHROME_VIEWS_IMAGE_VIEW_H__ diff --git a/chrome/views/label.cc b/chrome/views/label.cc new file mode 100644 index 0000000..590294b --- /dev/null +++ b/chrome/views/label.cc @@ -0,0 +1,406 @@ +// 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 "chrome/views/label.h" + +#include <math.h> + +#include "base/string_util.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" +#include "chrome/views/view_container.h" + +namespace ChromeViews { + +const char Label::kViewClassName[] = "chrome/views/Label"; + +static const SkColor kEnabledColor = SK_ColorBLACK; +static const SkColor kDisabledColor = SkColorSetRGB(161, 161, 146); + +Label::Label() { + Init(L"", GetDefaultFont()); +} + +Label::Label(const std::wstring& text) { + Init(text, GetDefaultFont()); +} + +Label::Label(const std::wstring& text, const ChromeFont& font) { + Init(text, font); +} + +void Label::Init(const std::wstring& text, const ChromeFont& font) { + contains_mouse_ = false; + font_ = font; + text_size_valid_ = false; + SetText(text); + url_set_ = false; + color_ = kEnabledColor; + horiz_alignment_ = ALIGN_CENTER; + is_multi_line_ = false; +} + +Label::~Label() { +} + +void Label::GetPreferredSize(CSize* out) { + DCHECK(out); + if (is_multi_line_) { + ChromeCanvas cc(0, 0, true); + int w = GetWidth(), h = 0; + cc.SizeStringInt(text_, font_, &w, &h, ComputeMultiLineFlags()); + out->cx = w; + out->cy = h; + } else { + GetTextSize(out); + } + + gfx::Insets insets = GetInsets(); + out->cx += insets.left() + insets.right(); + out->cy += insets.top() + insets.bottom(); +} + +int Label::ComputeMultiLineFlags() { + int flags = ChromeCanvas::MULTI_LINE; + switch (horiz_alignment_) { + case ALIGN_LEFT: + flags |= ChromeCanvas::TEXT_ALIGN_LEFT; + break; + case ALIGN_CENTER: + flags |= ChromeCanvas::TEXT_ALIGN_CENTER; + break; + case ALIGN_RIGHT: + flags |= ChromeCanvas::TEXT_ALIGN_RIGHT; + break; + } + return flags; +} + +void Label::Paint(ChromeCanvas* canvas) { + PaintBackground(canvas); + std::wstring paint_text; + + if (url_set_) { + // TODO(jungshik) : Figure out how to get 'intl.accept_languages' + // preference and use it when calling ElideUrl. + paint_text = gfx::ElideUrl(url_, font_, bounds_.right - bounds_.left, + std::wstring()); + + + // An URLs is always treated as an LTR text and therefore we should + // explicitly mark it as such if the locale is RTL so that URLs containing + // Hebrew or Arabic characters are displayed correctly. + // + // Note that we don't check the View's UI layout setting in order to + // determine whether or not to insert the special Unicode formatting + // characters. We use the locale settings because an URL is always treated + // as an LTR string, even if its containing view does not use an RTL UI + // layout. + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + l10n_util::WrapStringWithLTRFormatting(&paint_text); + } else { + paint_text = text_; + } + + if (is_multi_line_) { + canvas->DrawStringInt(paint_text, font_, color_, 0, 0, GetWidth(), + GetHeight(), ComputeMultiLineFlags()); + PaintFocusBorder(canvas); + } else { + gfx::Rect text_bounds = GetTextBounds(); + + canvas->DrawStringInt(paint_text, + font_, + color_, + text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height()); + // We'll draw the focus border ourselves so it is around the text. + if (HasFocus()) + canvas->DrawFocusRect(text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height()); + } +} + +void Label::PaintBackground(ChromeCanvas* canvas) { + const Background* bg = contains_mouse_ ? GetMouseOverBackground() : NULL; + if (!bg) + bg = GetBackground(); + if (bg) + bg->Paint(canvas, this); +} + +void Label::SetFont(const ChromeFont& font) { + font_ = font; + text_size_valid_ = false; + SchedulePaint(); +} + +ChromeFont Label::GetFont() const { + return font_; +} + +void Label::SetText(const std::wstring& text) { + text_ = text; + url_set_ = false; + text_size_valid_ = false; + SchedulePaint(); +} + +void Label::SetURL(const GURL& url) { + url_ = url; + text_ = UTF8ToWide(url_.spec()); + url_set_ = true; + text_size_valid_ = false; + SchedulePaint(); +} + +const std::wstring Label::GetText() const { + if (url_set_) + return UTF8ToWide(url_.spec()); + else + return text_; +} + +const GURL Label::GetURL() const { + if (url_set_) + return url_; + else + return GURL(text_); +} + +void Label::GetTextSize(CSize* out) { + if (!text_size_valid_) { + text_size_.cx = font_.GetStringWidth(text_); + text_size_.cy = font_.height(); + text_size_valid_ = true; + } + + if (text_size_valid_) { + *out = text_size_; + } else { + out->cx = out->cy = 0; + } +} + +int Label::GetHeightForWidth(int w) { + if (is_multi_line_) { + int h = 0; + ChromeCanvas cc(0, 0, true); + cc.SizeStringInt(text_, font_, &w, &h, ComputeMultiLineFlags()); + return h; + } + + return View::GetHeightForWidth(w); +} + +std::string Label::GetClassName() const { + return kViewClassName; +} + +void Label::SetColor(const SkColor& color) { + color_ = color; +} + +const SkColor Label::GetColor() const { + return color_; +} + +void Label::SetHorizontalAlignment(Alignment a) { + if (horiz_alignment_ != a) { + + // If the View's UI layout is right-to-left, we need to flip the alignment + // so that the alignment settings take into account the text + // directionality. + if (UILayoutIsRightToLeft()) { + if (a == ALIGN_LEFT) + a = ALIGN_RIGHT; + else if (a == ALIGN_RIGHT) + a = ALIGN_LEFT; + } + horiz_alignment_ = a; + SchedulePaint(); + } +} + +Label::Alignment Label::GetHorizontalAlignment() const { + return horiz_alignment_; +} + +void Label::SetMultiLine(bool f) { + if (f != is_multi_line_) { + is_multi_line_ = f; + SchedulePaint(); + } +} + +bool Label::IsMultiLine() { + return is_multi_line_; +} + +void Label::SetTooltipText(const std::wstring& tooltip_text) { + tooltip_text_ = tooltip_text; +} + +bool Label::GetTooltipText(int x, int y, std::wstring* tooltip) { + DCHECK(tooltip); + + // If a tooltip has been explicitly set, use it. + if (!tooltip_text_.empty()) { + tooltip->assign(tooltip_text_); + return true; + } + + // Show the full text if the text does not fit. + if (!is_multi_line_ && font_.GetStringWidth(text_) > GetWidth()) { + *tooltip = text_; + return true; + } + return false; +} + +void Label::OnMouseMoved(const MouseEvent& e) { + UpdateContainsMouse(e); +} + +void Label::OnMouseEntered(const MouseEvent& event) { + UpdateContainsMouse(event); +} + +void Label::OnMouseExited(const MouseEvent& event) { + SetContainsMouse(false); +} + +void Label::SetMouseOverBackground(Background* background) { + mouse_over_background_.reset(background); +} + +const Background* Label::GetMouseOverBackground() const { + return mouse_over_background_.get(); +} + +void Label::SetEnabled(bool enabled) { + if (enabled == enabled_) + return; + View::SetEnabled(enabled); + SetColor(enabled ? kEnabledColor : kDisabledColor); +} + +// static +ChromeFont Label::GetDefaultFont() { + return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); +} + +void Label::UpdateContainsMouse(const MouseEvent& event) { + SetContainsMouse(GetTextBounds().Contains(event.GetX(), event.GetY())); +} + +void Label::SetContainsMouse(bool contains_mouse) { + if (contains_mouse_ == contains_mouse) + return; + contains_mouse_ = contains_mouse; + if (GetMouseOverBackground()) + SchedulePaint(); +} + +gfx::Rect Label::GetTextBounds() { + CSize text_size; + GetTextSize(&text_size); + gfx::Insets insets = GetInsets(); + int avail_width = GetWidth() - insets.left() - insets.right(); + // Respect the size set by the owner view + text_size.cx = std::min(avail_width, static_cast<int>(text_size.cx)); + + int text_y = insets.top() + + (GetHeight() - text_size.cy - insets.top() - insets.bottom()) / 2; + int text_x; + switch (horiz_alignment_) { + case ALIGN_LEFT: + text_x = insets.left(); + break; + case ALIGN_CENTER: + // We put any extra margin pixel on the left rather than the right, since + // GetTextExtentPoint32() can report a value one too large on the right. + text_x = insets.left() + (avail_width + 1 - text_size.cx) / 2; + break; + case ALIGN_RIGHT: + text_x = GetWidth() - insets.right() - text_size.cx; + break; + } + return gfx::Rect(text_x, text_y, text_size.cx, text_size.cy); +} + +void Label::SizeToFit(int max_width) { + DCHECK(is_multi_line_); + + std::vector<std::wstring> lines; + SplitString(text_, L'\n', &lines); + + int width = 0; + for (std::vector<std::wstring>::const_iterator iter = lines.begin(); + iter != lines.end(); ++iter) { + width = std::max(width, font_.GetStringWidth(*iter)); + } + + if (max_width > 0) + width = std::min(width, max_width); + + CRect out; + GetBounds(&out); + SetBounds(out.left, out.top, width, 0); + SizeToPreferredSize(); +} + +bool Label::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_TEXT; + return true; +} + +bool Label::GetAccessibleName(std::wstring* name) { + *name = GetText(); + return true; +} + +bool Label::GetAccessibleState(VARIANT* state) { + DCHECK(state); + + state->lVal |= STATE_SYSTEM_READONLY; + return true; +} + +} diff --git a/chrome/views/label.h b/chrome/views/label.h new file mode 100644 index 0000000..70541a8 --- /dev/null +++ b/chrome/views/label.h @@ -0,0 +1,200 @@ +// 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. + +#ifndef CHROME_VIEWS_LABEL_H__ +#define CHROME_VIEWS_LABEL_H__ + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/view.h" +#include "googleurl/src/gurl.h" +#include "SkColor.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// Label class +// +// A label is a view subclass that can display a string. +// +///////////////////////////////////////////////////////////////////////////// +class Label : public View { + public: + enum Alignment { ALIGN_LEFT = 0, + ALIGN_CENTER, + ALIGN_RIGHT }; + + // The view class name. + static const char kViewClassName[]; + + // Create a new label with a default font and empty value + Label(); + + // Create a new label with a default font + explicit Label(const std::wstring& text); + + Label(const std::wstring& text, const ChromeFont& font); + + virtual ~Label(); + + // Overridden to compute the size required to display this label + virtual void GetPreferredSize(CSize* out); + + // Return the height necessary to display this label with the provided width. + // This method is used to layout multi-line labels. It is equivalent to + // GetPreferredSize().cy if the receiver is not multi-line + virtual int GetHeightForWidth(int w); + + // Returns chrome/views/Label. + virtual std::string GetClassName() const; + + // Overridden to paint + virtual void Paint(ChromeCanvas* canvas); + + // If the mouse is over the label, and a mouse over background has been + // specified, its used. Otherwise super's implementation is invoked + virtual void PaintBackground(ChromeCanvas* canvas); + + // Set the font. + void SetFont(const ChromeFont& font); + + // Return the font used by this label + ChromeFont GetFont() const; + + // Set the label text. + void SetText(const std::wstring& text); + + // Return the label text. + const std::wstring GetText() const; + + // Set URL Value - text_ is set to spec(). + void SetURL(const GURL& url); + + // Return the label URL. + const GURL GetURL() const; + + // Set the color + virtual void SetColor(const SkColor& color); + + // Return a reference to the currently used color + virtual const SkColor GetColor() const; + + // Alignment + void SetHorizontalAlignment(Alignment a); + Alignment GetHorizontalAlignment() const; + + // Set whether the label text can wrap on multiple lines. + // Default is false + void SetMultiLine(bool f); + + // Return whether the label text can wrap on multiple lines + bool IsMultiLine(); + + // Sets the tooltip text. Default behavior for a label (single-line) is to + // show the full text if it is wider than its bounds. Calling this overrides + // the default behavior and lets you set a custom tooltip. To revert to + // default behavior, call this with an empty string. + void SetTooltipText(const std::wstring& tooltip_text); + + // Gets the tooltip text for labels that are wider than their bounds, except + // when the label is multiline, in which case it just returns false (no + // tooltip). If a custom tooltip has been specified with SetTooltipText() + // it is returned instead. + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + // Mouse enter/exit are overridden to render mouse over background color. These + // invoke SetContainsMouse as necessary. + virtual void OnMouseMoved(const MouseEvent& e); + virtual void OnMouseEntered(const MouseEvent& event); + virtual void OnMouseExited(const MouseEvent& event); + + // The background color to use when the mouse is over the label. Label + // takes ownership of the Background. + void SetMouseOverBackground(Background* background); + const Background* GetMouseOverBackground() const; + + // Sets the enabled state. Setting the enabled state resets the color. + virtual void SetEnabled(bool enabled); + + // Resizes the label so its width is set to the width of the longest line and + // its height deduced accordingly. + // This is only intended for multi-line labels and is useful when the label's + // text contains several lines separated with \n. + // |max_width| is the maximum width that will be used (longer lines will be + // wrapped). If 0, no maximum width is enforced. + void SizeToFit(int max_width); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + // Overriden from View. + virtual bool GetAccessibleState(VARIANT* state); + + private: + static ChromeFont GetDefaultFont(); + + // If the mouse is over the text, SetContainsMouse(true) is invoked, otherwise + // SetContainsMouse(false) is invoked. + void Label::UpdateContainsMouse(const MouseEvent& event); + + // Updates whether the mouse is contained in the Label. If the new value + // differs from the current value, and a mouse over background is specified, + // SchedulePaint is invoked. + void SetContainsMouse(bool contains_mouse); + + // Returns where the text is drawn, in the receivers coordinate system. + gfx::Rect GetTextBounds(); + + int ComputeMultiLineFlags(); + void GetTextSize(CSize* out); + void Init(const std::wstring& text, const ChromeFont& font); + std::wstring text_; + GURL url_; + ChromeFont font_; + SkColor color_; + CSize text_size_; + bool text_size_valid_; + bool is_multi_line_; + bool url_set_; + Alignment horiz_alignment_; + std::wstring tooltip_text_; + // Whether the mouse is over this label. + bool contains_mouse_; + scoped_ptr<Background> mouse_over_background_; +}; + +} +#endif // CHROME_VIEWS_VIEW_H__ diff --git a/chrome/views/layout_manager.cc b/chrome/views/layout_manager.cc new file mode 100644 index 0000000..49725a9 --- /dev/null +++ b/chrome/views/layout_manager.cc @@ -0,0 +1,42 @@ +// 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 "chrome/views/layout_manager.h" + +#include "chrome/views/view.h" + +namespace ChromeViews { + +int LayoutManager::GetPreferredHeightForWidth(View* host, int width) { + CSize pref; + GetPreferredSize(host, &pref); + return pref.cy; +} + +} // namespace diff --git a/chrome/views/layout_manager.h b/chrome/views/layout_manager.h new file mode 100644 index 0000000..b2e135c --- /dev/null +++ b/chrome/views/layout_manager.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef CHROME_VIEWS_LAYOUT_MANAGER_H__ +#define CHROME_VIEWS_LAYOUT_MANAGER_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> + +namespace ChromeViews { + +class View; + +///////////////////////////////////////////////////////////////////////////// +// +// LayoutManager interface +// +// The LayoutManager interface provides methods to handle the sizing of +// the children of a View according to implementation-specific heuristics. +// +///////////////////////////////////////////////////////////////////////////// +class LayoutManager { + public: + virtual ~LayoutManager() {} + + // Notification that this LayoutManager has been installed on a particular + // host. + virtual void Installed(View* host) {} + + // Notification that this LayoutManager has been uninstalled on a particular + // host. + virtual void Uninstalled(View* host) {} + + // Lay out the children of |host| according to implementation-specific + // heuristics. The graphics used during painting is provided to allow for + // string sizing. + virtual void Layout(View* host) = 0; + + // Return the preferred size which is the size required to give each + // children their respective preferred size. + virtual void GetPreferredSize(View* host, CSize* out) = 0; + + // Returns the preferred height for the specified width. The default + // implementation returns the value from GetPreferredSize. + virtual int GetPreferredHeightForWidth(View* host, int width); + + // Notification that a view has been added. + virtual void ViewAdded(View* host, View* view) {} + + // Notification that a view has been removed. + virtual void ViewRemoved(View* host, View* view) {} +}; + +} + +#endif // CHROME_VIEWS_LAYOUT_MANAGER_H__ diff --git a/chrome/views/link.cc b/chrome/views/link.cc new file mode 100644 index 0000000..3b19aa1 --- /dev/null +++ b/chrome/views/link.cc @@ -0,0 +1,212 @@ +// 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 "chrome/views/link.h" + +#include "base/scoped_ptr.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/event.h" + +namespace ChromeViews { + +static HCURSOR g_hand_cursor = NULL; + +// Default colors used for links. +static const SkColor kHighlightedColor = SkColorSetRGB(255, 0x00, 0x00); +static const SkColor kNormalColor = SkColorSetRGB(0, 51, 153); +static const SkColor kDisabledColor = SkColorSetRGB(0, 0, 0); + +const char Link::kViewClassName[] = "chrome/views/Link"; + +Link::Link() : Label(L""), + controller_(NULL), + highlighted_(false), + highlighted_color_(kHighlightedColor), + disabled_color_(kDisabledColor), + normal_color_(kNormalColor) { + Init(); + SetFocusable(true); +} + +Link::Link(const std::wstring& title) : Label(title), + controller_(NULL), + highlighted_(false), + highlighted_color_(kHighlightedColor), + disabled_color_(kDisabledColor), + normal_color_(kNormalColor) { + Init(); + SetFocusable(true); +} + +void Link::Init() { + SetColor(normal_color_); + ValidateStyle(); +} + +Link::~Link() { +} + +void Link::SetController(LinkController* controller) { + controller_ = controller; +} + +const LinkController* Link::GetController() { + return controller_; +} + +std::string Link::GetClassName() const { + return kViewClassName; +} + +void Link::SetHighlightedColor(const SkColor& color) { + normal_color_ = color; + ValidateStyle(); +} + +void Link::SetDisabledColor(const SkColor& color) { + disabled_color_ = color; + ValidateStyle(); +} + +void Link::SetNormalColor(const SkColor& color) { + normal_color_ = color; + ValidateStyle(); +} + +bool Link::OnMousePressed(const MouseEvent& e) { + if (!enabled_ || (!e.IsLeftMouseButton() && !e.IsMiddleMouseButton())) + return false; + SetHighlighted(true); + return true; +} + +bool Link::OnMouseDragged(const MouseEvent& e) { + SetHighlighted(enabled_ && + (e.IsLeftMouseButton() || e.IsMiddleMouseButton()) && + HitTest(e.GetLocation())); + return true; +} + +void Link::OnMouseReleased(const MouseEvent& e, bool canceled) { + // Change the highlight first just in case this instance is deleted + // while calling the controller + SetHighlighted(false); + if (enabled_ && !canceled && + (e.IsLeftMouseButton() || e.IsMiddleMouseButton()) && + HitTest(e.GetLocation())) { + // Focus the link on click. + RequestFocus(); + + if (controller_) + controller_->LinkActivated(this, e.GetFlags()); + } +} + +bool Link::OnKeyPressed(const KeyEvent& e) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + SetHighlighted(true); + return true; + } + return false; +} + +bool Link::OnKeyReleased(const KeyEvent& e) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + SetHighlighted(false); + + // Focus the link on key pressed. + RequestFocus(); + + if (controller_) + controller_->LinkActivated(this, e.GetFlags()); + + return true; + } + return false; +} + +void Link::SetHighlighted(bool f) { + if (f != highlighted_) { + highlighted_ = f; + ValidateStyle(); + SchedulePaint(); + } +} + +void Link::ValidateStyle() { + ChromeFont font = GetFont(); + + if (enabled_) { + if ((font.style() & ChromeFont::UNDERLINED) == 0) { + Label::SetFont(font.DeriveFont(0, font.style() | + ChromeFont::UNDERLINED)); + } + } else { + if ((font.style() & ChromeFont::UNDERLINED) != 0) { + Label::SetFont(font.DeriveFont(0, font.style() & + ~ChromeFont::UNDERLINED)); + } + } + + if (enabled_) { + if (highlighted_) { + Label::SetColor(highlighted_color_); + } else { + Label::SetColor(normal_color_); + } + } else { + Label::SetColor(disabled_color_); + } +} + +void Link::SetFont(const ChromeFont& font) { + Label::SetFont(font); + ValidateStyle(); +} + +void Link::SetEnabled(bool f) { + if (f != enabled_) { + enabled_ = f; + ValidateStyle(); + SchedulePaint(); + } +} + +HCURSOR Link::GetCursorForPoint(Event::EventType event_type, int x, int y) { + if (enabled_) { + if (!g_hand_cursor) { + g_hand_cursor = LoadCursor(NULL, IDC_HAND); + } + return g_hand_cursor; + } else { + return NULL; + } +} + +} diff --git a/chrome/views/link.h b/chrome/views/link.h new file mode 100644 index 0000000..913f440 --- /dev/null +++ b/chrome/views/link.h @@ -0,0 +1,115 @@ +// 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. + +#ifndef CHROME_VIEWS_LINK_H__ +#define CHROME_VIEWS_LINK_H__ + +#include "chrome/views/label.h" + +namespace ChromeViews { + +class Link; + +//////////////////////////////////////////////////////////////////////////////// +// +// LinkController defines the method that should be implemented to +// receive a notification when a link is clicked +// +//////////////////////////////////////////////////////////////////////////////// +class LinkController { + public: + virtual void LinkActivated(Link* source, int event_flags) = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// Link class +// +// A Link is a label subclass that looks like an HTML link. It has a +// controller which is notified when a click occurs. +// +//////////////////////////////////////////////////////////////////////////////// +class Link : public Label { + public: + static const char Link::kViewClassName[]; + + Link(); + Link(const std::wstring& title); + virtual ~Link(); + + void SetController(LinkController* controller); + const LinkController* GetController(); + + // Overridden from View: + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, + bool canceled); + virtual bool OnKeyPressed(const KeyEvent& e); + virtual bool OnKeyReleased(const KeyEvent& e); + + virtual void SetFont(const ChromeFont& font); + + // Set whether the link is enabled. + virtual void SetEnabled(bool f); + + virtual HCURSOR GetCursorForPoint(Event::EventType event_type, int x, int y); + + virtual std::string GetClassName() const; + + void SetHighlightedColor(const SkColor& color); + void SetDisabledColor(const SkColor& color); + void SetNormalColor(const SkColor& color); + + private: + + // A highlighted link is clicked. + void SetHighlighted(bool f); + + // Make sure the label style matched the current state. + void ValidateStyle(); + + void Init(); + + LinkController* controller_; + + // Whether the link is currently highlighted. + bool highlighted_; + + // The color when the link is highlighted. + SkColor highlighted_color_; + + // The color when the link is disabled. + SkColor disabled_color_; + + // The color when the link is neither highlighted nor disabled. + SkColor normal_color_; +}; +} +#endif // CHROME_VIEWS_LINK_H__ diff --git a/chrome/views/menu.cc b/chrome/views/menu.cc new file mode 100644 index 0000000..037782a --- /dev/null +++ b/chrome/views/menu.cc @@ -0,0 +1,495 @@ +// 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 <windows.h> +#include <atlbase.h> +#include <atlcrack.h> +#include <atlapp.h> +#include <atlframe.h> + +#include "chrome/views/menu.h" + +#include "base/gfx/rect.h" +#include "chrome/views/accelerator.h" +#include "chrome/views/controller.h" +#include "base/string_util.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/common/win_util.h" + +const SkBitmap* Menu::Delegate::kEmptyIcon = 0; + +// The width of an icon, including the pixels between the icon and +// the item label. +static const int kIconWidth = 23; +// Margins between the top of the item and the label. +static const int kItemTopMargin = 3; +// Margins between the bottom of the item and the label. +static const int kItemBottomMargin = 4; +// Margins between the left of the item and the icon. +static const int kItemLeftMargin = 4; +// Margins between the right of the item and the label. +static const int kItemRightMargin = 10; +// The width for displaying the sub-menu arrow. +static const int kArrowWidth = 10; + +// Current active MenuHostWindow. If NULL, no menu is active. +static MenuHostWindow* active_host_window = NULL; + +namespace { + +static int ChromeGetMenuItemID(HMENU hMenu, int pos) { + // The built-in Windows ::GetMenuItemID doesn't work for submenus, + // so here's our own implementation. + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_ID; + GetMenuItemInfo(hMenu, pos, TRUE, &mii); + return mii.wID; +} + +// MenuHostWindow ------------------------------------------------------------- + +// MenuHostWindow is the HWND the HMENU is parented to. MenuHostWindow is used +// to intercept right clicks on the HMENU and notify the delegate as well as +// for drawing icons. +// +class MenuHostWindow : public CWindowImpl<MenuHostWindow, CWindow, + CWinTraits<WS_CHILD>> { + public: + MenuHostWindow(Menu* menu, HWND parent_window) : menu_(menu) { + int extended_style = 0; + // If the menu needs to be created with a right-to-left UI layout, we must + // set the appropriate RTL flags (such as WS_EX_LAYOUTRTL) property for the + // underlying HWND. + if (menu_->delegate_->IsRightToLeftUILayout()) + extended_style |= l10n_util::GetExtendedStyles(); + Create(parent_window, gfx::Rect().ToRECT(), 0, 0, extended_style); + } + + ~MenuHostWindow() { + DestroyWindow(); + } + + DECLARE_FRAME_WND_CLASS(L"MenuHostWindow", NULL); + BEGIN_MSG_MAP(MenuHostWindow); + MSG_WM_RBUTTONUP(OnRButtonUp) + MSG_WM_MEASUREITEM(OnMeasureItem) + MSG_WM_DRAWITEM(OnDrawItem) + END_MSG_MAP(); + + private: + // NOTE: I really REALLY tried to use WM_MENURBUTTONUP, but I ran into + // two problems in using it: + // 1. It doesn't contain the coordinates of the mouse. + // 2. It isn't invoked for menuitems representing a submenu that have children + // menu items (not empty). + + void OnRButtonUp(UINT w_param, const CPoint& loc) { + int id; + if (menu_->delegate_ && FindMenuIDByLocation(menu_, loc, &id)) + menu_->delegate_->ShowContextMenu(menu_, id, loc.x, loc.y, true); + } + + void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* lpmis) { + Menu::ItemData* data = reinterpret_cast<Menu::ItemData*>(lpmis->itemData); + if (data != NULL) { + ChromeFont font; + lpmis->itemWidth = font.GetStringWidth(data->label) + kIconWidth + + kItemLeftMargin + kItemRightMargin - + GetSystemMetrics(SM_CXMENUCHECK); + if (data->submenu) + lpmis->itemWidth += kArrowWidth; + lpmis->itemHeight = font.height() + kItemBottomMargin + kItemTopMargin; + } else { + // Measure separator size. + lpmis->itemHeight = GetSystemMetrics(SM_CYMENU) / 2; + lpmis->itemWidth = 0; + } + } + + void OnDrawItem(UINT wParam, DRAWITEMSTRUCT* lpdis) { + HDC hDC = lpdis->hDC; + COLORREF prev_bg_color, prev_text_color; + + // Set background color and text color + if (lpdis->itemState & ODS_SELECTED) { + prev_bg_color = SetBkColor(hDC, GetSysColor(COLOR_HIGHLIGHT)); + prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } else { + prev_bg_color = SetBkColor(hDC, GetSysColor(COLOR_MENU)); + if (lpdis->itemState & ODS_DISABLED) + prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT)); + else + prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_MENUTEXT)); + } + + if (lpdis->itemData) { + Menu::ItemData* data = + reinterpret_cast<Menu::ItemData*>(lpdis->itemData); + wchar_t* str = const_cast<wchar_t*>(data->label.c_str()); + + // Draw the background. + HBRUSH hbr = CreateSolidBrush(GetBkColor(hDC)); + FillRect(hDC, &lpdis->rcItem, hbr); + DeleteObject(hbr); + + // Draw the label. + RECT rect = lpdis->rcItem; + rect.top += kItemTopMargin; + rect.left += kItemLeftMargin + kIconWidth; + UINT format = DT_TOP | DT_LEFT | DT_SINGLELINE; + // Check whether the mnemonics should be underlined. + BOOL underline_mnemonics; + SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &underline_mnemonics, 0); + if (!underline_mnemonics) + format |= DT_HIDEPREFIX; + DrawTextEx(hDC, str, static_cast<int>(data->label.size()), + &rect, format, NULL); + + // Draw the icon after the label, otherwise it would be covered + // by the label. + if (data->icon.width() != 0 && data->icon.height() != 0) { + ChromeCanvas canvas(data->icon.width(), data->icon.height(), false); + canvas.drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode); + canvas.DrawBitmapInt(data->icon, 0, 0); + canvas.getTopPlatformDevice().drawToHDC(hDC, lpdis->rcItem.left + + kItemLeftMargin, lpdis->rcItem.top + (lpdis->rcItem.bottom - + lpdis->rcItem.top - data->icon.height()) / 2, NULL); + } + + } else { + // Draw the separator + lpdis->rcItem.top += (lpdis->rcItem.bottom - lpdis->rcItem.top) / 3; + DrawEdge(hDC, &lpdis->rcItem, EDGE_ETCHED, BF_TOP); + } + + SetBkColor(hDC, prev_bg_color); + SetTextColor(hDC, prev_text_color); + } + + bool FindMenuIDByLocation(Menu* menu, const CPoint& loc, int* id) { + int index = MenuItemFromPoint(NULL, menu->menu_, loc); + if (index != -1) { + *id = ChromeGetMenuItemID(menu->menu_, index); + return true; + } else { + for (std::vector<Menu*>::iterator i = menu->submenus_.begin(); + i != menu->submenus_.end(); ++i) { + if (FindMenuIDByLocation(*i, loc, id)) + return true; + } + } + return false; + } + + // The menu that created us. + Menu* menu_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuHostWindow); +}; + +} // namespace + +Menu::Menu(Delegate* delegate, AnchorPoint anchor, HWND owner) + : delegate_(delegate), + menu_(CreatePopupMenu()), + anchor_(anchor), + owner_(owner), + is_menu_visible_(false), + owner_draw_(false) { + DCHECK(delegate_); +} + +Menu::Menu(Menu* parent) + : delegate_(parent->delegate_), + menu_(CreatePopupMenu()), + anchor_(parent->anchor_), + owner_(parent->owner_), + is_menu_visible_(false), + owner_draw_(false) { +} + +Menu::~Menu() { + STLDeleteContainerPointers(submenus_.begin(), submenus_.end()); + STLDeleteContainerPointers(item_data_.begin(), item_data_.end()); + DestroyMenu(menu_); +} + +UINT Menu::GetStateFlagsForItemID(int item_id) const { + // Use the delegate to get enabled and checked state. + UINT flags = + delegate_->IsCommandEnabled(item_id) ? MFS_ENABLED : MFS_DISABLED; + + if (delegate_->IsItemChecked(item_id)) + flags |= MFS_CHECKED; + + if (delegate_->IsItemDefault(item_id)) + flags |= MFS_DEFAULT; + + return flags; +} + +void Menu::AppendMenuItemInternal(int item_id, + const std::wstring& label, + const SkBitmap& icon, + HMENU submenu, + MenuItemType type) { + MENUITEMINFO mii; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE | MIIM_ID; + if (submenu) { + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = submenu; + } + + // Set the type and ID. + if (!owner_draw_) { + mii.fType = MFT_STRING; + mii.fMask |= MIIM_STRING; + } else { + mii.fType = MFT_OWNERDRAW; + } + + if (type == RADIO) + mii.fType |= MFT_RADIOCHECK; + + mii.wID = item_id; + + // Set the item data. + Menu::ItemData* data = new ItemData; + item_data_.push_back(data); + data->submenu = submenu != NULL; + + std::wstring actual_label(label.empty() ? + delegate_->GetLabel(item_id) : label); + + // Find out if there is a shortcut we need to append to the label. + ChromeViews::Accelerator accelerator(0, false, false, false); + if (delegate_->GetAcceleratorInfo(item_id, &accelerator)) { + actual_label += L'\t'; + actual_label += accelerator.GetShortcutText(); + } + labels_.push_back(actual_label); + + if (owner_draw_) { + if (icon.width() != 0 && icon.height() != 0) + data->icon = icon; + else + data->icon = delegate_->GetIcon(item_id); + } else { + mii.dwTypeData = const_cast<wchar_t*>(labels_.back().c_str()); + } + + InsertMenuItem(menu_, -1, TRUE, &mii); +} + +Menu* Menu::AppendSubMenu(int item_id, + const std::wstring& label) { + return AppendSubMenuWithIcon(item_id, label, SkBitmap()); +} + +Menu* Menu::AppendSubMenuWithIcon(int item_id, + const std::wstring& label, + const SkBitmap& icon) { + if (!owner_draw_ && icon.width() != 0 && icon.height() != 0) + owner_draw_ = true; + + Menu* submenu = new Menu(this); + submenus_.push_back(submenu); + AppendMenuItemInternal(item_id, label, icon, submenu->menu_, NORMAL); + return submenu; +} + +void Menu::AppendSeparator() { + AppendMenu(menu_, MF_SEPARATOR, 0, NULL); +} + +DWORD Menu::GetTPMAlignFlags() const { + // The manner in which we handle the menu alignment depends on whether or not + // the menu is displayed within a mirrored view. If the UI is mirrored, the + // alignment needs to be fliped so that instead of aligning the menu to the + // right of the point, we align it to the left and vice versa. + DWORD align_flags = TPM_TOPALIGN; + switch (anchor_) { + case TOPLEFT: + if (delegate_->IsRightToLeftUILayout()) { + align_flags |= TPM_RIGHTALIGN; + } else { + align_flags |= TPM_LEFTALIGN; + } + break; + + case TOPRIGHT: + if (delegate_->IsRightToLeftUILayout()) { + align_flags |= TPM_LEFTALIGN; + } else { + align_flags |= TPM_RIGHTALIGN; + } + break; + + default: + NOTREACHED(); + return 0; + } + return align_flags; +} + +bool Menu::SetIcon(const SkBitmap& icon, int item_id) { + if (!owner_draw_) + owner_draw_ = true; + + const int num_items = GetMenuItemCount(menu_); + int sep_count = 0; + for (int i = 0; i < num_items; ++i) { + if (!(GetMenuState(menu_, i, MF_BYPOSITION) & MF_SEPARATOR)) { + if (ChromeGetMenuItemID(menu_, i) == item_id) { + item_data_[i - sep_count]->icon = icon; + // When the menu is running, we use SetMenuItemInfo to let Windows + // update the item information so that the icon being displayed + // could change immediately. + if (active_host_window) { + MENUITEMINFO mii; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE | MIIM_DATA; + mii.fType = MFT_OWNERDRAW; + mii.dwItemData = + reinterpret_cast<ULONG_PTR>(item_data_[i - sep_count]); + SetMenuItemInfo(menu_, item_id, false, &mii); + } + return true; + } + } else { + ++sep_count; + } + } + + // Continue searching for the item in submenus. + for (size_t i = 0; i < submenus_.size(); ++i) { + if (submenus_[i]->SetIcon(icon, item_id)) + return true; + } + + return false; +} + +void Menu::SetMenuInfo() { + const int num_items = GetMenuItemCount(menu_); + int sep_count = 0; + for (int i = 0; i < num_items; ++i) { + MENUITEMINFO mii_info; + mii_info.cbSize = sizeof(mii_info); + // Get the menu's original type. + mii_info.fMask = MIIM_FTYPE; + GetMenuItemInfo(menu_, i, MF_BYPOSITION, &mii_info); + // Set item states. + if (!(mii_info.fType & MF_SEPARATOR)) { + const int id = ChromeGetMenuItemID(menu_, i); + + MENUITEMINFO mii; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE | MIIM_FTYPE | MIIM_DATA | MIIM_STRING; + // We also need MFT_STRING for owner drawn items in order to let Windows + // handle the accelerators for us. + mii.fType = MFT_STRING; + if (owner_draw_) + mii.fType |= MFT_OWNERDRAW; + // If the menu originally has radiocheck type, we should follow it. + if (mii_info.fType & MFT_RADIOCHECK) + mii.fType |= MFT_RADIOCHECK; + mii.fState = GetStateFlagsForItemID(id); + + // Validate the label. If there is a contextual label, use it, otherwise + // default to the static label + std::wstring label; + if (!delegate_->GetContextualLabel(id, &label)) + label = labels_[i - sep_count]; + + if (owner_draw_) { + item_data_[i - sep_count]->label = label; + mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data_[i - sep_count]); + } + mii.dwTypeData = const_cast<wchar_t*>(label.c_str()); + mii.cch = static_cast<UINT>(label.size()); + SetMenuItemInfo(menu_, i, true, &mii); + } else { + // Set data for owner drawn separators. Set dwItemData NULL to indicate + // a separator. + if (owner_draw_) { + MENUITEMINFO mii; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR | MFT_OWNERDRAW; + mii.dwItemData = NULL; + SetMenuItemInfo(menu_, i, true, &mii); + } + ++sep_count; + } + } + + for (size_t i = 0; i < submenus_.size(); ++i) + submenus_[i]->SetMenuInfo(); +} + +void Menu::RunMenuAt(int x, int y) { + SetMenuInfo(); + + delegate_->MenuWillShow(); + + // Show the menu. Blocks until the menu is dismissed or an item is chosen. + UINT flags = + GetTPMAlignFlags() | TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE; + is_menu_visible_ = true; + DCHECK(owner_); + // In order for context menus on menus to work, the context menu needs to + // share the same window as the first menu is parented to. + bool created_host = false; + if (!active_host_window) { + created_host = true; + active_host_window = new MenuHostWindow(this, owner_); + } + UINT selected_id = + TrackPopupMenuEx(menu_, flags, x, y, active_host_window->m_hWnd, NULL); + if (created_host) { + delete active_host_window; + active_host_window = NULL; + } + is_menu_visible_ = false; + + // Execute the chosen command + if (selected_id != 0) + delegate_->ExecuteCommand(selected_id); +} + +void Menu::Cancel() { + DCHECK(is_menu_visible_); + EndMenu(); +} diff --git a/chrome/views/menu.h b/chrome/views/menu.h new file mode 100644 index 0000000..8b9a5ff --- /dev/null +++ b/chrome/views/menu.h @@ -0,0 +1,369 @@ +// 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. + +#ifndef CHROME_VIEWS_MENU_H__ +#define CHROME_VIEWS_MENU_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> +#include <vector> + +#include "base/message_loop.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/controller.h" +#include "skia/include/SkBitmap.h" + +namespace { +class MenuHostWindow; +} + +namespace ChromeViews { +class Accelerator; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Menu class +// +// A wrapper around a Win32 HMENU handle that provides convenient APIs for +// menu construction, display and subsequent command execution. +// +/////////////////////////////////////////////////////////////////////////////// +class Menu { + friend class MenuHostWindow; + + public: + ///////////////////////////////////////////////////////////////////////////// + // + // Delegate Interface + // + // Classes implement this interface to tell the menu system more about each + // item as it is created. + // + ///////////////////////////////////////////////////////////////////////////// + class Delegate : public Controller { + public: + // Whether or not an item should be shown as checked. + virtual bool IsItemChecked(int id) const { + return false; + } + + // Whether or not an item should be shown as the default (using bold). + // There can only be one default menu item. + virtual bool IsItemDefault(int id) const { + return false; + } + + // The string shown for the menu item. + virtual std::wstring GetLabel(int id) const { + return std::wstring(); + } + + // The delegate needs to implement this function if it wants to display + // the shortcut text next to each menu item. If there is an accelerator + // for a given item id, the implementor must return it. + virtual bool GetAcceleratorInfo(int id, ChromeViews::Accelerator* accel) { + return false; + } + + // The icon shown for the menu item. + virtual const SkBitmap& GetIcon(int id) const { + return GetEmptyIcon(); + } + + // The number of items to show in the menu + virtual int GetItemCount() const { + return 0; + } + + // Whether or not an item is a separator. + virtual bool IsItemSeparator(int id) const { + return false; + } + + // Shows the context menu with the specified id. This is invoked when the + // user does the appropriate gesture to show a context menu. The id + // identifies the id of the menu to show the context menu for. + // is_mouse_gesture is true if this is the result of a mouse gesture. + // If this is not the result of a mouse gesture x/y is the recommended + // location to display the content menu at. In either case, x/y is in + // screen coordinates. + virtual void ShowContextMenu(Menu* source, + int id, + int x, + int y, + bool is_mouse_gesture) { + } + + // Whether an item has an icon. + virtual bool HasIcon(int id) const { + return false; + } + + // Notification that the menu is about to be popped up. + virtual void MenuWillShow() { + } + + // Whether to create a right-to-left menu. The default implementation + // returns true if the locale's language is a right-to-left language (such + // as Hebrew) and false otherwise. This is generally the right behavior + // since there is no reason to show left-to-right menus for right-to-left + // locales. However, subclasses can override this behavior so that the menu + // is a right-to-left menu only if the view's layout is right-to-left + // (since the view can use a different layout than the locale's language + // layout). + virtual bool IsRightToLeftUILayout() const { + return l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT; + } + + // Controller + virtual bool SupportsCommand(int id) const { + return true; + } + virtual bool IsCommandEnabled(int id) const { + return true; + } + virtual bool GetContextualLabel(int id, std::wstring* out) const { + return false; + } + virtual void ExecuteCommand(int id) { + } + + protected: + // Returns an empty icon. Will initialize kEmptyIcon if it hasn't been + // initialized. + const SkBitmap& GetEmptyIcon() const { + if (kEmptyIcon == NULL) + kEmptyIcon = new SkBitmap(); + return *kEmptyIcon; + } + + private: + // Will be initialized to an icon of 0 width and 0 height when first using. + // An empty icon means we don't need to draw it. + static const SkBitmap* kEmptyIcon; + }; + + // This class is a helper that simply wraps a controller and forwards all + // state and execution actions to it. Use this when you're not defining your + // own custom delegate, but just hooking a context menu to some existing + // controller elsewhere. + class BaseControllerDelegate : public Delegate { + public: + explicit BaseControllerDelegate(Controller* wrapped) + : controller_(wrapped) { + } + + // Overridden from Menu::Delegate + virtual bool SupportsCommand(int id) const { + return controller_->SupportsCommand(id); + } + virtual bool IsCommandEnabled(int id) const { + return controller_->IsCommandEnabled(id); + } + virtual void ExecuteCommand(int id) { + controller_->ExecuteCommand(id); + } + virtual bool GetContextualLabel(int id, std::wstring* out) const { + return controller_->GetContextualLabel(id, out); + } + + private: + // The internal controller that we wrap to forward state and execution + // actions to. + Controller* controller_; + + DISALLOW_EVIL_CONSTRUCTORS(BaseControllerDelegate); + }; + + // How this popup should align itself relative to the point it is run at. + enum AnchorPoint { + TOPLEFT, + TOPRIGHT + }; + + // Different types of menu items + enum MenuItemType { + NORMAL, + CHECKBOX, + RADIO, + SEPARATOR + }; + + // The data of menu items needed to display. + struct ItemData { + std::wstring label; + SkBitmap icon; + bool submenu; + }; + + // Construct a Menu using the specified controller to determine command + // state. + // delegate A Menu::Delegate implementation that provides more + // information about the Menu presentation. + // anchor An alignment hint for the popup menu. + // owner The window that the menu is being brought up relative + // to. Not actually used for anything but must not be + // NULL. + Menu(Delegate* delegate, AnchorPoint anchor, HWND owner); + virtual ~Menu(); + + // Adds an item to this menu. + // item_id The id of the item, used to identify it in delegate callbacks + // or (if delegate is NULL) to identify the command associated + // with this item with the controller specified in the ctor. Note + // that this value should not be 0 as this has a special meaning + // ("NULL command, no item selected") + // label The text label shown. + // type The type of item. + void AppendMenuItem(int item_id, + const std::wstring& label, + MenuItemType type) { + if (type == SEPARATOR) + AppendSeparator(); + else + AppendMenuItemInternal(item_id, label, SkBitmap(), NULL, type); + } + + // Append a submenu to this menu. + // The returned pointer is owned by this menu. + Menu* AppendSubMenu(int item_id, + const std::wstring& label); + + // Append a submenu with an icon to this menu + // The returned pointer is owned by this menu. + // Unless the icon is empty, calling this function forces the Menu class + // to draw the menu, instead of relying on Windows. + Menu* AppendSubMenuWithIcon(int item_id, + const std::wstring& label, + const SkBitmap& icon); + + // This is a convenience for standard text label menu items where the label + // is provided with this call. + void AppendMenuItemWithLabel(int item_id, + const std::wstring& label) { + AppendMenuItem(item_id, label, Menu::NORMAL); + } + + // This is a convenience for text label menu items where the label is + // provided by the delegate. + void AppendDelegateMenuItem(int item_id) { + AppendMenuItem(item_id, std::wstring(), Menu::NORMAL); + } + + // Adds a separator to this menu + void AppendSeparator(); + + // Appends a menu item with an icon. This is for the menu item which + // needs an icon. Calling this function forces the Menu class to draw + // the menu, instead of relying on Windows. + void AppendMenuItemWithIcon(int item_id, + const std::wstring& label, + const SkBitmap& icon) { + if (!owner_draw_) + owner_draw_ = true; + AppendMenuItemInternal(item_id, label, icon, NULL, Menu::NORMAL); + } + + // Sets an icon for an item with a given item_id. Calling this function + // also forces the Menu class to draw the menu, instead of relying on Windows. + // Returns false if the item with |item_id| is not found. + bool SetIcon(const SkBitmap& icon, int item_id); + + // Shows the menu, blocks until the user dismisses the menu or selects an + // item, and executes the command for the selected item (if any). + // Warning: Blocking call. Will implicitly run a message loop. + void RunMenuAt(int x, int y); + + // Cancels the menu. + virtual void Cancel(); + + protected: + // The delegate that is being used to get information about the presentation. + Delegate* delegate_; + + private: + explicit Menu(Menu* parent); + + void AppendMenuItemInternal(int item_id, + const std::wstring& label, + const SkBitmap& icon, + HMENU submenu, + MenuItemType type); + + // Sets menu information before displaying, including sub-menus. + void SetMenuInfo(); + + // Get all the state flags for the |fState| field of MENUITEMINFO for the + // item with the specified id. |delegate| is consulted if non-NULL about + // the state of the item in preference to |controller_|. + UINT GetStateFlagsForItemID(int item_id) const; + + // Gets the Win32 TPM alignment flags for the specified AnchorPoint. + DWORD GetTPMAlignFlags() const; + + // The Win32 Menu Handle we wrap + HMENU menu_; + + // The window that would receive WM_COMMAND messages when the user selects + // an item from the menu. + HWND owner_; + + // This list is used to store the default labels for the menu items. + // We may use contextual labels when RunMenu is called, so we must save + // a copy of default ones here. + std::vector<std::wstring> labels_; + + // A flag to indicate whether this menu will be drawn by the Menu class. + // If it's true, all the menu items will be owner drawn. Otherwise, + // all the drawing will be done by Windows. + bool owner_draw_; + + // How this popup menu should be aligned relative to the point it is run at. + AnchorPoint anchor_; + + // This list is to store the string labels and icons to display. It's used + // when owner_draw_ is true. We give MENUITEMINFO pointers to these + // structures to specify what we'd like to draw. If owner_draw_ is false, + // we only give MENUITEMINFO pointers to the labels_. + // The label member of the ItemData structure comes from either labels_ or + // the GetContextualLabel. + std::vector<ItemData*> item_data_; + + // Our sub-menus, if any. + std::vector<Menu*> submenus_; + + // Whether the menu is visible. + bool is_menu_visible_; + + DISALLOW_EVIL_CONSTRUCTORS(Menu); +}; + +#endif // CHROME_VIEWS_MENU_H__ diff --git a/chrome/views/menu_button.cc b/chrome/views/menu_button.cc new file mode 100644 index 0000000..1ebaba4 --- /dev/null +++ b/chrome/views/menu_button.cc @@ -0,0 +1,280 @@ +// 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 <atlbase.h> +#include <atlapp.h> + +#include "chrome/views/menu_button.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/button.h" +#include "chrome/views/event.h" +#include "chrome/views/root_view.h" +#include "chrome/views/view_menu_delegate.h" +#include "chrome/views/view_container.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +// The amount of time, in milliseconds, we wait before allowing another mouse +// pressed event to show the menu. +static const int64 kMinimumTimeBetweenButtonClicks = 100; + +// The down arrow used to differentiate the menu button from normal +// text buttons. +static const SkBitmap* kMenuMarker = NULL; + +// How much padding to put on the left and right of the menu marker. +static const int kMenuMarkerPaddingLeft = 3; +static const int kMenuMarkerPaddingRight = -1; + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +MenuButton::MenuButton(const std::wstring& text, + ViewMenuDelegate* menu_delegate, + bool show_menu_marker) + : TextButton(text), + menu_visible_(false), + menu_closed_time_(Time::Now()), + menu_delegate_(menu_delegate), + show_menu_marker_(show_menu_marker) { + if (kMenuMarker == NULL) { + kMenuMarker = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_MENU_MARKER); + } + SetTextAlignment(TextButton::ALIGN_LEFT); +} + +MenuButton::~MenuButton() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - Public APIs +// +//////////////////////////////////////////////////////////////////////////////// + +void MenuButton::GetPreferredSize(CSize* result) { + TextButton::GetPreferredSize(result); + if (show_menu_marker_) { + result->cx += kMenuMarker->width() + kMenuMarkerPaddingLeft + + kMenuMarkerPaddingRight; + } +} + +void MenuButton::Paint(ChromeCanvas* canvas, bool for_drag) { + TextButton::Paint(canvas, for_drag); + + if (show_menu_marker_) { + gfx::Insets insets = GetInsets(); + + // We can not use the ChromeViews' mirroring infrastructure for mirroring + // a MenuButton control (see TextButton::Paint() for a detailed + // explanation regarding why we can not flip the canvas). Therefore, we + // need to manually mirror the position of the down arrow. + gfx::Rect arrow_bounds(GetWidth() - insets.right() - + kMenuMarker->width() - kMenuMarkerPaddingRight, + GetHeight() / 2, + kMenuMarker->width(), + kMenuMarker->height()); + arrow_bounds.set_x(MirroredLeftPointForRect(arrow_bounds)); + canvas->DrawBitmapInt(*kMenuMarker, arrow_bounds.x(), arrow_bounds.y()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - Events +// +//////////////////////////////////////////////////////////////////////////////// + +int MenuButton::GetMaximumScreenXCoordinate() { + ViewContainer* vc = GetViewContainer(); + + if (!vc) { + NOTREACHED(); + return 0; + } + + HWND hwnd = vc->GetHWND(); + CRect t; + ::GetWindowRect(hwnd, &t); + + gfx::Rect r(t); + gfx::Rect monitor_rect = win_util::GetMonitorBoundsForRect(r); + return monitor_rect.x() + monitor_rect.width() - 1; +} + +bool MenuButton::Activate() { + SetState(BS_PUSHED); + // We need to synchronously paint here because subsequently we enter a + // menu modal loop which will stop this window from updating and + // receiving the paint message that should be spawned by SetState until + // after the menu closes. + PaintNow(); + if (menu_delegate_) { + CRect lb; + GetLocalBounds(&lb, true); + + // The position of the menu depends on whether or not the locale is + // right-to-left. + CPoint menu_position = lb.BottomRight(); + if (UILayoutIsRightToLeft()) + menu_position.x = lb.left; + + View::ConvertPointToScreen(this, &menu_position); + if (UILayoutIsRightToLeft()) + menu_position.Offset(2, -4); + else + menu_position.Offset(-2, -4); + + int max_x_coordinate = GetMaximumScreenXCoordinate(); + if (max_x_coordinate && max_x_coordinate <= menu_position.x) + menu_position.x = max_x_coordinate - 1; + + // We're about to show the menu from a mouse press. By showing from the + // mouse press event we block RootView in mouse dispatching. This also + // appears to cause RootView to get a mouse pressed BEFORE the mouse + // release is seen, which means RootView sends us another mouse press no + // matter where the user pressed. To force RootView to recalculate the + // mouse target during the mouse press we explicitly set the mouse handler + // to NULL. + GetRootView()->SetMouseHandler(NULL); + + menu_visible_ = true; + menu_delegate_->RunMenu(this, menu_position, GetViewContainer()->GetHWND()); + menu_visible_ = false; + menu_closed_time_ = Time::Now(); + + // Now that the menu has closed, we need to manually reset state to + // "normal" since the menu modal loop will have prevented normal + // mouse move messages from getting to this View. We set "normal" + // and not "hot" because the likelihood is that the mouse is now + // somewhere else (user clicked elsewhere on screen to close the menu + // or selected an item) and we will inevitably refresh the hot state + // in the event the mouse _is_ over the view. + SetState(BS_NORMAL); + SchedulePaint(); + + // We must return false here so that the RootView does not get stuck + // sending all mouse pressed events to us instead of the appropriate + // target. + return false; + } + return true; +} + +bool MenuButton::OnMousePressed(const ChromeViews::MouseEvent& e) { + using namespace ChromeViews; + if (IsFocusable()) + RequestFocus(); + if (GetState() != BS_DISABLED) { + // If we're draggable (GetDragOperations returns a non-zero value), then + // don't pop on press, instead wait for release. + if (e.IsOnlyLeftMouseButton() && HitTest(e.GetLocation()) && + GetDragOperations(e.GetX(), e.GetY()) == DragDropTypes::DRAG_NONE) { + TimeDelta delta = Time::Now() - menu_closed_time_; + int64 delta_in_milliseconds = delta.InMilliseconds(); + if (delta_in_milliseconds > kMinimumTimeBetweenButtonClicks) { + return Activate(); + } + } + } + return true; +} + +void MenuButton::OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled) { + if (GetDragOperations(e.GetX(), e.GetY()) != DragDropTypes::DRAG_NONE && + GetState() != BS_DISABLED && !canceled && !InDrag() && + e.IsOnlyLeftMouseButton() && HitTest(e.GetLocation())) { + Activate(); + } else { + TextButton::OnMouseReleased(e, canceled); + } +} + +// When the space bar or the enter key is pressed we need to show the menu. +bool MenuButton::OnKeyReleased(const KeyEvent& e) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + return Activate(); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool MenuButton::GetAccessibleDefaultAction(std::wstring* action) { + DCHECK(action); + + action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS)); + return true; +} + +bool MenuButton::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN; + return true; +} + +bool MenuButton::GetAccessibleState(VARIANT* state) { + DCHECK(state); + + state->lVal |= STATE_SYSTEM_HASPOPUP; + return true; +} + +// The reason we override View::OnMouseExited is because we get this event when +// we display the menu. If we don't override this method then +// BaseButton::OnMouseExited will get the event and will set the button's state +// to BS_NORMAL instead of keeping the state BM_PUSHED. This, in turn, will +// cause the button to appear depressed while the menu is displayed. +void MenuButton::OnMouseExited(const MouseEvent& event) { + using namespace ChromeViews; + if ((state_ != BS_DISABLED) && (!menu_visible_) && (!InDrag())) { + SetState(BS_NORMAL); + } +} + +} // namespace ChromeViews diff --git a/chrome/views/menu_button.h b/chrome/views/menu_button.h new file mode 100644 index 0000000..42f4a255 --- /dev/null +++ b/chrome/views/menu_button.h @@ -0,0 +1,122 @@ +// 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. + +#ifndef CHROME_VIEWS_MENU_BUTTON_H__ +#define CHROME_VIEWS_MENU_BUTTON_H__ + +#include <windows.h> + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/background.h" +#include "chrome/views/text_button.h" +#include "base/time.h" + +namespace ChromeViews { + +class MouseEvent; +class ViewMenuDelegate; + + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton +// +// A button that shows a menu when the left mouse button is pushed +// +//////////////////////////////////////////////////////////////////////////////// +class MenuButton : public TextButton { + public: + // + // Create a Button + MenuButton(const std::wstring& text, + ViewMenuDelegate* menu_delegate, + bool show_menu_marker); + virtual ~MenuButton(); + + // Activate the button (called when the button is pressed). + virtual bool Activate(); + + // Overridden to take into account the potential use of a drop marker. + void GetPreferredSize(CSize* result); + virtual void Paint(ChromeCanvas* canvas, bool for_drag); + + // These methods are overriden to implement a simple push button + // behavior + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + void OnMouseReleased(const ChromeViews::MouseEvent& e, bool canceled); + virtual bool OnKeyReleased(const KeyEvent& e); + virtual void OnMouseExited(const MouseEvent& event); + + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. + bool GetAccessibleDefaultAction(std::wstring* action); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + // Overriden from View. + virtual bool GetAccessibleState(VARIANT* state); + + protected: + // true if the menu is currently visible. + bool menu_visible_; + + private: + + // Compute the maximum X coordinate for the current screen. MenuButtons + // use this to make sure a menu is never shown off screen. + int GetMaximumScreenXCoordinate(); + + DISALLOW_EVIL_CONSTRUCTORS(MenuButton); + + // We use a time object in order to keep track of when the menu was closed. + // The time is used for simulating menu behavior for the menu button; that + // is, if the menu is shown and the button is pressed, we need to close the + // menu. There is no clean way to get the second click event because the + // menu is displayed using a modal loop and, unlike regular menus in Windows, + // the button is not part of the displayed menu. + Time menu_closed_time_; + + // The associated menu's resource identifier. + ViewMenuDelegate* menu_delegate_; + + // Whether or not we're showing a drop marker. + bool show_menu_marker_; + + friend class TextButtonBackground; +}; + +} // namespace + +#endif // CHROME_VIEWS_MENU_BUTTON_H__ diff --git a/chrome/views/message_box_view.cc b/chrome/views/message_box_view.cc new file mode 100644 index 0000000..0425a19 --- /dev/null +++ b/chrome/views/message_box_view.cc @@ -0,0 +1,206 @@ +// 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 "chrome/views/message_box_view.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/browser/controller.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/client_view.h" + +#include "generated_resources.h" + +static const int kDefaultMessageWidth = 320; + +/////////////////////////////////////////////////////////////////////////////// +// MessageBoxView, public: + +MessageBoxView::MessageBoxView(int dialog_flags, + const std::wstring& message, + const std::wstring& default_prompt, + int message_width) + : message_label_(new ChromeViews::Label(message)), + prompt_field_(NULL), + icon_(NULL), + check_box_(NULL), + message_width_(message_width), + focus_grabber_factory_(this) { + Init(dialog_flags, default_prompt); +} + +MessageBoxView::MessageBoxView(int dialog_flags, + const std::wstring& message, + const std::wstring& default_prompt) + : message_label_(new ChromeViews::Label(message)), + prompt_field_(NULL), + icon_(NULL), + check_box_(NULL), + message_width_(kDefaultMessageWidth), + focus_grabber_factory_(this) { + Init(dialog_flags, default_prompt); +} + +std::wstring MessageBoxView::GetInputText() { + if (prompt_field_) + return prompt_field_->GetText(); + return EmptyWString(); +} + +bool MessageBoxView::IsCheckBoxSelected() { + if (check_box_ == NULL) + return false; + return check_box_->IsSelected(); +} + +void MessageBoxView::SetIcon(const SkBitmap& icon) { + if (!icon_) + icon_ = new ChromeViews::ImageView(); + icon_->SetImage(icon); + icon_->SetBounds(0, 0, icon.width(), icon.height()); + ResetLayoutManager(); +} + +void MessageBoxView::SetCheckBoxLabel(const std::wstring& label) { + if (!check_box_) + check_box_ = new ChromeViews::CheckBox(label); + else + check_box_->SetLabel(label); + ResetLayoutManager(); +} + +/////////////////////////////////////////////////////////////////////////////// +// MessageBoxView, ChromeViews::View overrides: + +void MessageBoxView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (child == this && is_add) { + if (prompt_field_) + prompt_field_->SelectAll(); + MessageLoop::current()->PostTask(FROM_HERE, + focus_grabber_factory_.NewRunnableMethod( + &MessageBoxView::FocusFirstFocusableControl)); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MessageBoxView, private: + +void MessageBoxView::FocusFirstFocusableControl() { + if (prompt_field_) + prompt_field_->RequestFocus(); + else if (check_box_) + check_box_->RequestFocus(); + else + RequestFocus(); +} + +void MessageBoxView::Init(int dialog_flags, + const std::wstring& default_prompt) { + message_label_->SetMultiLine(true); + message_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + + if (dialog_flags & kFlagHasPromptField) { + prompt_field_ = new ChromeViews::TextField; + prompt_field_->SetText(default_prompt); + } + + ResetLayoutManager(); +} + +void MessageBoxView::ResetLayoutManager() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + // Initialize the Grid Layout Manager used for this dialog box. + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + CSize icon_size; + if (icon_) + icon_->GetPreferredSize(&icon_size); + + // Add the column set for the message displayed at the top of the dialog box. + // And an icon, if one has been set. + const int message_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(message_column_view_set_id); + if (icon_) { + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::FIXED, icon_size.cx, icon_size.cy); + column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing); + } + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, message_width_, 0); + + // Column set for prompt textfield, if one has been set. + const int textfield_column_view_set_id = 1; + if (prompt_field_) { + column_set = layout->AddColumnSet(textfield_column_view_set_id); + if (icon_) { + column_set->AddPaddingColumn(0, + icon_size.cx + kUnrelatedControlHorizontalSpacing); + } + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + } + + // Column set for checkbox, if one has been set. + const int checkbox_column_view_set_id = 2; + if (check_box_) { + column_set = layout->AddColumnSet(checkbox_column_view_set_id); + if (icon_) { + column_set->AddPaddingColumn(0, + icon_size.cx + kUnrelatedControlHorizontalSpacing); + } + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + } + + layout->StartRow(0, message_column_view_set_id); + if (icon_) + layout->AddView(icon_); + + layout->AddView(message_label_); + + if (prompt_field_) { + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, textfield_column_view_set_id); + layout->AddView(prompt_field_); + } + + if (check_box_) { + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, checkbox_column_view_set_id); + layout->AddView(check_box_); + } + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); +} diff --git a/chrome/views/message_box_view.h b/chrome/views/message_box_view.h new file mode 100644 index 0000000..4e9c0bd --- /dev/null +++ b/chrome/views/message_box_view.h @@ -0,0 +1,125 @@ +// 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. + +#ifndef CHROME_VIEWS_MESSAGE_BOX_VIEW_VIEW_H__ +#define CHROME_VIEWS_MESSAGE_BOX_VIEW_VIEW_H__ + +#include <string> + +#include "base/task.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view.h" + +// This class displays a message box within a constrained window +// with options for a message, a prompt, and OK and Cancel buttons. +class MessageBoxView : public ChromeViews::View { + public: + // flags + static const int kFlagHasOKButton = 0x1; + static const int kFlagHasCancelButton = 0x2; + static const int kFlagHasPromptField = 0x4; + static const int kFlagHasMessage = 0x8; + + static const int kIsConfirmMessageBox = kFlagHasMessage | + kFlagHasOKButton | + kFlagHasCancelButton; + static const int kIsJavascriptAlert = kFlagHasOKButton | kFlagHasMessage; + static const int kIsJavascriptConfirm = kIsJavascriptAlert | + kFlagHasCancelButton; + static const int kIsJavascriptPrompt = kIsJavascriptConfirm | + kFlagHasPromptField; + + MessageBoxView(int dialog_flags, + const std::wstring& message, + const std::wstring& default_prompt, + int message_width); + + MessageBoxView(int dialog_flags, + const std::wstring& message, + const std::wstring& default_prompt); + + // Returns user entered data in the prompt field. + std::wstring GetInputText(); + + // Returns true if a checkbox is selected, false otherwise. (And false if + // the message box has no checkbox.) + bool IsCheckBoxSelected(); + + // Adds |icon| to the upper left of the message box or replaces the current + // icon. To start out, the message box has no icon. + void SetIcon(const SkBitmap& icon); + + // Adds a checkbox with the specified label to the message box if this is the + // first call. Otherwise, it changes the label of the current checkbox. To + // start, the message box has no checkbox until this function is called. + void SetCheckBoxLabel(const std::wstring& label); + + protected: + // Layout and Painting functions. + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Called after ViewHierarchyChanged's call stack unwinds and returns to the + // message loop to focus the first focusable element in the dialog box. + void FocusFirstFocusableControl(); + + // Sets up the layout manager and initializes the prompt field. This should + // only be called once, from the constructor. + void Init(int dialog_flags, const std::wstring& default_prompt); + + // Sets up the layout manager based on currently initialized views. Should be + // called when a view is initialized or changed. + void ResetLayoutManager(); + + // Message for the message box. + ChromeViews::Label* message_label_; + + // Input text field for the message box. + ChromeViews::TextField* prompt_field_; + + // Icon displayed in the upper left corner of the message box. + ChromeViews::ImageView* icon_; + + // Checkbox for the message box. + ChromeViews::CheckBox* check_box_; + + // Maximum width of the message label. + int message_width_; + + ScopedRunnableMethodFactory<MessageBoxView> focus_grabber_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(MessageBoxView); +}; + +#endif // CHROME_VIEWS_MESSAGE_BOX_VIEW_VIEW_H__ diff --git a/chrome/views/native_button.cc b/chrome/views/native_button.cc new file mode 100644 index 0000000..97b66e1 --- /dev/null +++ b/chrome/views/native_button.cc @@ -0,0 +1,212 @@ +// 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 "chrome/views/native_button.h" + +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" + +namespace ChromeViews { + +NativeButton::NativeButton(const std::wstring& label) { + Init(label, false); +} + +NativeButton::NativeButton(const std::wstring& label, bool is_default) { + Init(label, is_default); +} + +NativeButton::~NativeButton() { +} + +void NativeButton::SetListener(Listener *l) { + listener_ = l; +} + +void NativeButton::SetPadding(CSize size) { + padding_ = size; +} + +void NativeButton::GetPreferredSize(CSize *out) { + HWND hwnd = GetNativeControlHWND(); + if (hwnd) { + SIZE sz = {0, 0}; + ::SendMessage(hwnd, + BCM_GETIDEALSIZE, + 0, + reinterpret_cast<LPARAM>(&sz)); + sz.cx += 2 * padding_.cx; + sz.cy += 2 * padding_.cy; + + if (min_dlu_size_.width()) + sz.cx = std::max(static_cast<int>(sz.cx), + font_.horizontal_dlus_to_pixels(min_dlu_size_.width())); + if (min_dlu_size_.height()) + sz.cy = std::max(static_cast<int>(sz.cy), + font_.vertical_dlus_to_pixels(min_dlu_size_.height())); + + *out = sz; + } +} + +void NativeButton::SetLabel(const std::wstring& l) { + // Even though we create a flipped HWND for a native button when the locale + // is right-to-left, Windows does not render text for the button using a + // right-to-left context (perhaps because the parent HWND is not flipped). + // The result is that RTL strings containing punctuation marks are not + // displayed properly. For example, the string "...ABC" (where A, B and C are + // Hebrew characters) is displayed as "ABC..." which is incorrect. + // + // In order to overcome this problem, we mark the localized Hebrew strings as + // RTL strings explicitly (using the appropriate Unicode formatting) so that + // Windows displays the text correctly regardless of the HWND hierarchy. + std::wstring localized_label; + if (l10n_util::AdjustStringForLocaleDirection(l, &localized_label)) + label_.assign(localized_label); + else + label_.assign(l); + + SetAccessibleName(l); + UpdateNativeButton(); +} + +const std::wstring NativeButton::GetLabel() const { + return label_; +} + +HWND NativeButton::CreateNativeControl(HWND parent_container) { + DWORD flags = WS_CHILD | BS_PUSHBUTTON; + if (is_default_) + flags |= BS_DEFPUSHBUTTON; + HWND r = ::CreateWindowEx(GetAdditionalExStyle(), L"BUTTON", L"", flags, 0, 0, + GetWidth(), GetHeight(), parent_container, NULL, + NULL, NULL); + SendMessage(r, WM_SETFONT, reinterpret_cast<WPARAM>(font_.hfont()), FALSE); + ConfigureNativeButton(r); + return r; +} + +LRESULT NativeButton::OnNotify(int w_param, LPNMHDR l_param) { + return 0; +} + +LRESULT NativeButton::OnCommand(UINT code, int id, HWND source) { + if (code == BN_CLICKED) + Clicked(); + return 0; +} + +void NativeButton::UpdateNativeButton() { + HWND hwnd = GetNativeControlHWND(); + if (hwnd) + ConfigureNativeButton(hwnd); +} + +void NativeButton::ConfigureNativeButton(HWND hwnd) { + ::SetWindowText(hwnd, label_.c_str()); +} + +bool NativeButton::AcceleratorPressed(const Accelerator& accelerator) { + if (enabled_) { + Clicked(); + return true; + } + return false; +} + +bool NativeButton::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_PUSHBUTTON; + return true; +} + +bool NativeButton::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void NativeButton::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +void NativeButton::Init(const std::wstring& label, bool is_default) { + // Marking the string as an RTL string if the locale is RTL. Refer to + // the comments in NativeButton::SetLabel for more details. + std::wstring localized_label; + if (l10n_util::AdjustStringForLocaleDirection(label, &localized_label)) + label_.assign(localized_label); + else + label_.assign(label); + + l10n_util::AdjustStringForLocaleDirection(label, &label_); + listener_ = NULL; + SetAccessibleName(label); + // The padding of 8 is a bit arbitrary, there appears to be no way to + // get a recommended padding, and this value varies greatly among windows + // dialogs. + // + // The min size in DLUs comes from + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp + padding_ = CSize(8, 0); + is_default_ = is_default; + min_dlu_size_.SetSize(50, 14); + SetFocusable(true); + if (is_default) + AddAccelerator(Accelerator(VK_RETURN, false, false, false)); +} + +void NativeButton::Clicked() { + DCHECK(enabled_); + // Give the focus to the button. + if (IsFocusable()) + RequestFocus(); + + if (listener_) + listener_->ButtonPressed(this); +} + +bool NativeButton::NotifyOnKeyDown() const { + return true; +} + +bool NativeButton::OnKeyDown(int virtual_key_code) { + if (virtual_key_code == VK_RETURN) { + Clicked(); + return true; + } + return false; +} + +} diff --git a/chrome/views/native_button.h b/chrome/views/native_button.h new file mode 100644 index 0000000..788e2bc --- /dev/null +++ b/chrome/views/native_button.h @@ -0,0 +1,156 @@ +// 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. + +#ifndef CHROME_VIEWS_NATIVE_BUTTON_H__ +#define CHROME_VIEWS_NATIVE_BUTTON_H__ + +#include <string> + +#include "base/gfx/size.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/native_control.h" + +namespace ChromeViews { + + +class HWNDView; + +//////////////////////////////////////////////////////////////////////////////// +// +// NativeButton is a wrapper for a windows native button +// TODO(beng): (Cleanup) this should derive also from some button-like base to +// get all the listenery-stuff for free and to work with +// AddManagedButton. +// +//////////////////////////////////////////////////////////////////////////////// +class NativeButton : public NativeControl { + public: + explicit NativeButton(const std::wstring& label); + NativeButton(const std::wstring& label, bool is_default); + virtual ~NativeButton(); + + virtual void GetPreferredSize(CSize *out); + + void SetLabel(const std::wstring& l); + const std::wstring GetLabel() const; + + class Listener { + public: + // + // This is invoked once the button is released. + virtual void ButtonPressed(NativeButton* sender) = 0; + }; + + // + // The the listener, the object that receives a notification when this + // button is pressed. + void SetListener(Listener* listener); + + // Set the font used by this button. + void SetFont(const ChromeFont& font) { font_ = font; } + + // Adds some internal padding to the button. The |size| specified is applied + // on both sides of the button for each directions + void SetPadding(CSize size); + + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + // Invoked when the accelerator associated with the button is pressed. + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // Sets the minimum size of the button from the specified size (in dialog + // units). If the width/height is non-zero, the preferred size of the button + // is max(preferred size of the content + padding, dlus converted to pixels). + // + // The default is 50, 14. + void SetMinSizeFromDLUs(const gfx::Size& dlu_size) { + min_dlu_size_ = dlu_size; + } + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Assigns an accessible string name. + void SetAccessibleName(const std::wstring& name); + + protected: + + virtual HWND CreateNativeControl(HWND parent_container); + + // Sub-classes can call this method to cause the native button to reflect + // the current state + virtual void UpdateNativeButton(); + + // Sub-classes must override this method to properly configure the native + // button given the current state + virtual void ConfigureNativeButton(HWND hwnd); + + // Overridden from NativeControl so we can activate the button when Enter is + // pressed. + virtual bool NotifyOnKeyDown() const; + virtual bool OnKeyDown(int virtual_key_code); + + private: + NativeButton() {} + + // Initializes the button. If |is_default| is true, this appears like the + // "default" button, and will register Enter as its keyboard accelerator. + void Init(const std::wstring& label, bool is_default); + + void Clicked(); + + std::wstring label_; + ChromeFont font_; + Listener* listener_; + + CSize padding_; + + // True if the button should be rendered to appear like the "default" button + // in the containing dialog box. Default buttons register Enter as their + // accelerator. + bool is_default_; + + // Minimum size, in dlus (see SetMinSizeFromDLUs). + gfx::Size min_dlu_size_; + + // Storage of strings needed for accessibility. + std::wstring accessible_name_; + + DISALLOW_EVIL_CONSTRUCTORS(NativeButton); +}; + +} + +#endif // CHROME_VIEWS_NATIVE_BUTTON_H__ diff --git a/chrome/views/native_control.cc b/chrome/views/native_control.cc new file mode 100644 index 0000000..d9ef5a9 --- /dev/null +++ b/chrome/views/native_control.cc @@ -0,0 +1,381 @@ +// 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 "chrome/views/native_control.h" + +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlframe.h> + +#include "base/win_util.h" +#include "chrome/views/border.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/view_container.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/background.h" +#include "base/gfx/native_theme.h" + +namespace ChromeViews { + +// Maps to the original WNDPROC for the controller window before we subclassed +// it. +static const wchar_t* const kHandlerKey = + L"__CONTROL_ORIGINAL_MESSAGE_HANDLER__"; + +// Maps to the NativeControl. +static const wchar_t* const kNativeControlKey = L"__NATIVE_CONTROL__"; + +class NativeControlContainer : public CWindowImpl<NativeControlContainer, + CWindow, + CWinTraits<WS_CHILD | WS_CLIPSIBLINGS | + WS_CLIPCHILDREN>> { + public: + + explicit NativeControlContainer(NativeControl* parent) : parent_(parent), + control_(NULL) { + Create(parent->GetViewContainer()->GetHWND()); + ::ShowWindow(m_hWnd, SW_SHOW); + } + + virtual ~NativeControlContainer() { + } + + // NOTE: If you add a new message, be sure and verify parent_ is valid before + // calling into parent_. + DECLARE_FRAME_WND_CLASS(L"ChromeViewsNativeControlContainer", NULL); + BEGIN_MSG_MAP(NativeControlContainer); + MSG_WM_CREATE(OnCreate); + MSG_WM_ERASEBKGND(OnEraseBkgnd); + MSG_WM_PAINT(OnPaint); + MSG_WM_SIZE(OnSize); + MSG_WM_NOTIFY(OnNotify); + MSG_WM_COMMAND(OnCommand); + MSG_WM_DESTROY(OnDestroy); + MSG_WM_CONTEXTMENU(OnContextMenu); + MSG_WM_CTLCOLORBTN(OnCtlColorBtn); + MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic) + END_MSG_MAP(); + + HWND GetControl() { + return control_; + } + + // Called when the parent is getting deleted. This control stays around until + // it gets the OnFinalMessage call. + void ResetParent() { + parent_ = NULL; + } + + void OnFinalMessage(HWND hwnd) { + if (parent_) + parent_->NativeControlDestroyed(); + delete this; + } + private: + + LRESULT OnCreate(LPCREATESTRUCT create_struct) { + control_ = parent_->CreateNativeControl(m_hWnd); + FocusManager::InstallFocusSubclass(control_, parent_); + if (parent_->NotifyOnKeyDown()) { + // We subclass the control hwnd so we get the WM_KEYDOWN messages. + WNDPROC original_handler = + win_util::SetWindowProc(control_, + &NativeControl::NativeControlWndProc); + SetProp(control_, kHandlerKey, original_handler); + SetProp(control_, kNativeControlKey , parent_); + } + ::ShowWindow(control_, SW_SHOW); + return 1; + } + + LRESULT OnEraseBkgnd(HDC dc) { + return 1; + } + + void OnPaint(HDC ignore) { + PAINTSTRUCT ps; + HDC dc = ::BeginPaint(*this, &ps); + ::EndPaint(*this, &ps); + } + + void OnSize(int type, const CSize& sz) { + ::MoveWindow(control_, 0, 0, sz.cx, sz.cy, TRUE); + } + + LRESULT OnCommand(UINT code, int id, HWND source) { + return parent_ ? parent_->OnCommand(code, id, source) : 0; + } + + LRESULT OnNotify(int w_param, LPNMHDR l_param) { + if (parent_) + return parent_->OnNotify(w_param, l_param); + else + return 0; + } + + void OnDestroy() { + if (parent_) + parent_->OnDestroy(); + } + + void OnContextMenu(HWND window, const CPoint& location) { + if (parent_) + parent_->OnContextMenu(location); + } + + // We need to find an ancestor with a non-null background, and + // ask it for a (solid color) brush that approximates + // the background. The caller will use this when drawing + // the native control as a background color, particularly + // for radiobuttons and XP style pushbuttons. + LRESULT OnCtlColor(UINT msg, HDC dc, HWND control) { + const View *ancestor = parent_; + while (ancestor) { + const Background *background = ancestor->GetBackground(); + if (background) { + HBRUSH brush = background->GetNativeControlBrush(); + if (brush) + return reinterpret_cast<LRESULT>(brush); + } + ancestor = ancestor->GetParent(); + } + + // COLOR_BTNFACE is the default for dialog box backgrounds. + return reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_BTNFACE)); + } + + LRESULT OnCtlColorBtn(HDC dc, HWND control) { + return OnCtlColor(WM_CTLCOLORBTN, dc, control); + } + + LRESULT OnCtlColorStatic(HDC dc, HWND control) { + return OnCtlColor(WM_CTLCOLORSTATIC, dc, control); + } + + NativeControl* parent_; + HWND control_; + DISALLOW_EVIL_CONSTRUCTORS(NativeControlContainer); +}; + +NativeControl::NativeControl() : hwnd_view_(NULL), + container_(NULL), + fixed_width_(-1), + horizontal_alignment_(CENTER), + fixed_height_(-1), + vertical_alignment_(CENTER) { + enabled_ = true; + focusable_ = true; +} + +NativeControl::~NativeControl() { + if (container_) { + container_->ResetParent(); + ::DestroyWindow(*container_); + } +} + +void NativeControl::ValidateNativeControl() { + if (hwnd_view_ == NULL) { + hwnd_view_ = new HWNDView(); + AddChildView(hwnd_view_); + } + + if (!container_ && IsVisible()) { + container_ = new NativeControlContainer(this); + hwnd_view_->Attach(*container_); + if (!enabled_) + EnableWindow(GetNativeControlHWND(), enabled_); + + // This message ensures that the focus border is shown. + ::SendMessage(container_->GetControl(), + WM_CHANGEUISTATE, + MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS), + 0); + } +} + +void NativeControl::ViewHierarchyChanged(bool is_add, View *parent, + View *child) { + if (is_add && GetViewContainer()) { + ValidateNativeControl(); + Layout(); + } +} + +void NativeControl::Layout() { + if (!container_ && GetViewContainer()) + ValidateNativeControl(); + + if (hwnd_view_) { + CRect lb; + GetLocalBounds(&lb, false); + + int x = lb.left; + int y = lb.top; + int width = lb.Width(); + int height = lb.Height(); + if (fixed_width_ > 0) { + width = std::min(fixed_width_, width); + switch (horizontal_alignment_) { + case LEADING: + // Nothing to do. + break; + case CENTER: + x += (lb.Width() - width) / 2; + break; + case TRAILING: + x = x + lb.Width() - width; + break; + default: + NOTREACHED(); + } + } + + if (fixed_height_ > 0) { + height = std::min(fixed_height_, height); + switch (vertical_alignment_) { + case LEADING: + // Nothing to do. + break; + case CENTER: + y += (lb.Height() - height) / 2; + break; + case TRAILING: + y = y + lb.Height() - height; + break; + default: + NOTREACHED(); + } + } + + hwnd_view_->SetBounds(x, y, width, height); + } +} + +void NativeControl::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void NativeControl::Focus() { + if (container_) { + DCHECK(container_->GetControl()); + ::SetFocus(container_->GetControl()); + } +} + +HWND NativeControl::GetNativeControlHWND() { + if (container_) + return container_->GetControl(); + else + return NULL; +} + +void NativeControl::NativeControlDestroyed() { + if (hwnd_view_) + hwnd_view_->Detach(); + container_ = NULL; +} + +void NativeControl::SetVisible(bool f) { + if (f != IsVisible()) { + View::SetVisible(f); + if (!f && container_) { + ::DestroyWindow(*container_); + } else if (f && !container_) { + ValidateNativeControl(); + } + } +} + +void NativeControl::SetEnabled(bool enabled) { + if (enabled_ != enabled) { + View::SetEnabled(enabled); + if (GetNativeControlHWND()) { + EnableWindow(GetNativeControlHWND(), enabled_); + } + } +} + +void NativeControl::Paint(ChromeCanvas* canvas) { +} + +void NativeControl::VisibilityChanged(View* starting_from, bool is_visible) { + SetVisible(is_visible); +} + +void NativeControl::SetFixedWidth(int width, Alignment alignment) { + DCHECK(width > 0); + fixed_width_ = width; + horizontal_alignment_ = alignment; +} + +void NativeControl::SetFixedHeight(int height, Alignment alignment) { + DCHECK(height > 0); + fixed_height_ = height; + vertical_alignment_ = alignment; +} + +DWORD NativeControl::GetAdditionalExStyle() const { + // If the UI for the view is mirrored, we should make sure we add the + // extended window style for a right-to-left layout so the subclass creates + // a mirrored HWND for the underlying control. + DWORD ex_style = 0; + if (UILayoutIsRightToLeft()) + ex_style |= l10n_util::GetExtendedStyles(); + + return ex_style; +} + +// static +LRESULT CALLBACK NativeControl::NativeControlWndProc(HWND window, UINT message, + WPARAM w_param, + LPARAM l_param) { + HANDLE original_handler = GetProp(window, kHandlerKey); + DCHECK(original_handler); + NativeControl* native_control = + static_cast<NativeControl*>(GetProp(window, kNativeControlKey)); + DCHECK(native_control); + + if (message == WM_KEYDOWN) { + if (native_control->OnKeyDown(static_cast<int>(w_param))) + return 0; + } else if (message == WM_DESTROY) { + win_util::SetWindowProc(window, + reinterpret_cast<WNDPROC>(original_handler)); + RemoveProp(window, kHandlerKey); + RemoveProp(window, kNativeControlKey); + } + + return CallWindowProc(reinterpret_cast<WNDPROC>(original_handler), window, + message, w_param, l_param); +} + +} diff --git a/chrome/views/native_control.h b/chrome/views/native_control.h new file mode 100644 index 0000000..5191c7e --- /dev/null +++ b/chrome/views/native_control.h @@ -0,0 +1,151 @@ +// 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. + +#ifndef CHROME_VIEWS_NATIVE_CONTROL_H__ +#define CHROME_VIEWS_NATIVE_CONTROL_H__ + +#include "chrome/views/view.h" + +namespace ChromeViews { + +class HWNDView; +class NativeControlContainer; +class RootView; + +//////////////////////////////////////////////////////////////////////////////// +// +// NativeControl is an abstract view that is used to implement views wrapping +// native controls. Subclasses can simply implement CreateNativeControl() to +// wrap a new kind of control +// +//////////////////////////////////////////////////////////////////////////////// +class NativeControl : public View { + public: + enum Alignment { + LEADING = 0, + CENTER, + TRAILING }; + + NativeControl(); + virtual ~NativeControl(); + + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + virtual void Layout(); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Overridden to properly set the native control state. + virtual void SetVisible(bool f); + virtual void SetEnabled(bool enabled); + + // Overridden to do nothing. + virtual void Paint(ChromeCanvas* canvas); + protected: + friend class NativeControlContainer; + + // Overridden by sub-classes to create the windows control which is wrapped + virtual HWND CreateNativeControl(HWND parent_container) = 0; + + // Invoked when the native control sends a WM_NOTIFY message to its parent + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param) = 0; + + // Invoked when the native control sends a WM_COMMAND message to its parent + virtual LRESULT OnCommand(UINT code, int id, HWND source) { return 0; } + + // Invoked when the appropriate gesture for a context menu is issued. + virtual void OnContextMenu(const CPoint& location) {} + + // Overridden so to set the native focus to the native control. + virtual void Focus(); + + // Invoked when the native control sends a WM_DESTORY message to its parent. + virtual void OnDestroy() { } + + // Return the native control + virtual HWND GetNativeControlHWND(); + + // Invoked by the native windows control when it has been destroyed. This is + // invoked AFTER WM_DESTORY has been sent. Any window commands send to the + // HWND will most likely fail. + void NativeControlDestroyed(); + + // Overridden so that the control properly reflects parent's visibility. + virtual void VisibilityChanged(View* starting_from, bool is_visible); + + // Controls that have fixed sizes should call these methods to specify the + // actual size and how they should be aligned within their parent. + void SetFixedWidth(int width, Alignment alignment); + void SetFixedHeight(int height, Alignment alignment); + + // Derived classes interested in receiving key down notification should + // override this method and return true. In which case OnKeyDown is called + // when a key down message is sent to the control. + // Note that this method is called at the time of the control creation: the + // behavior will not change if the returned value changes after the control + // has been created. + virtual bool NotifyOnKeyDown() const { return false; } + + // Invoked when a key is pressed on the control (if NotifyOnKeyDown returns + // true). Should return true if the key message was processed, false + // otherwise. + virtual bool OnKeyDown(int virtual_key_code) { return false; } + + // Returns additional extended style flags. When subclasses call + // CreateWindowEx in order to create the underlying control, they must OR the + // ExStyle parameter with the value returned by this function. + // + // We currently use this method in order to add flags such as WS_EX_LAYOUTRTL + // to the HWND for views with right-to-left UI layout. + DWORD GetAdditionalExStyle() const; + + // This variable is protected to provide subclassers direct access. However + // subclassers should always check for NULL since this variable is only + // initialized in ValidateNativeControl(). + HWNDView* hwnd_view_; + + // Fixed size information. -1 for a size means no fixed size. + int fixed_width_; + Alignment horizontal_alignment_; + int fixed_height_; + Alignment vertical_alignment_; + + private: + + void ValidateNativeControl(); + + static LRESULT CALLBACK NativeControlWndProc(HWND window, UINT message, + WPARAM w_param, LPARAM l_param); + + NativeControlContainer* container_; + + DISALLOW_EVIL_CONSTRUCTORS(NativeControl); +}; + +} + +#endif // CHROME_VIEWS_NATIVE_CONTROL_H__ diff --git a/chrome/views/native_scroll_bar.cc b/chrome/views/native_scroll_bar.cc new file mode 100644 index 0000000..49ceb76c --- /dev/null +++ b/chrome/views/native_scroll_bar.cc @@ -0,0 +1,391 @@ +// 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 "chrome/views/native_scroll_bar.h" + +#include <math.h> +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlframe.h> + +#include "base/message_loop.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/view_container.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// ScrollBarContainer +// +// Since windows scrollbar only send notifications to their parent hwnd, we +// use instance of this class to wrap native scrollbars. +// +///////////////////////////////////////////////////////////////////////////// +class ScrollBarContainer : public CWindowImpl<ScrollBarContainer, + CWindow, + CWinTraits<WS_CHILD>> { + public: + ScrollBarContainer(ScrollBar* parent) : parent_(parent), + scrollbar_(NULL) { + Create(parent->GetViewContainer()->GetHWND()); + ::ShowWindow(m_hWnd, SW_SHOW); + } + + virtual ~ScrollBarContainer() { + } + + DECLARE_FRAME_WND_CLASS(L"ChromeViewsScrollBarContainer", NULL); + BEGIN_MSG_MAP(ScrollBarContainer); + MSG_WM_CREATE(OnCreate); + MSG_WM_ERASEBKGND(OnEraseBkgnd); + MSG_WM_PAINT(OnPaint); + MSG_WM_SIZE(OnSize); + MSG_WM_HSCROLL(OnHorizScroll); + MSG_WM_VSCROLL(OnVertScroll); + END_MSG_MAP(); + + HWND GetScrollBarHWND() { + return scrollbar_; + } + + // Invoked when the scrollwheel is used + void ScrollWithOffset(int o) { + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + ::GetScrollInfo(scrollbar_, SB_CTL, &si); + int pos = si.nPos - o; + + if (pos < parent_->GetMinPosition()) + pos = parent_->GetMinPosition(); + else if (pos > parent_->GetMaxPosition()) + pos = parent_->GetMaxPosition(); + + ScrollBarController* sbc = parent_->GetController(); + sbc->ScrollToPosition(parent_, pos); + + si.nPos = pos; + si.fMask = SIF_POS; + ::SetScrollInfo(scrollbar_, SB_CTL, &si, TRUE); + } + + private: + + LRESULT OnCreate(LPCREATESTRUCT create_struct) { + scrollbar_ = CreateWindow(L"SCROLLBAR", + L"", + WS_CHILD | (parent_->IsHorizontal() ? + SBS_HORZ : SBS_VERT), + 0, + 0, + parent_->GetWidth(), + parent_->GetHeight(), + m_hWnd, + NULL, + NULL, + NULL); + ::ShowWindow(scrollbar_, SW_SHOW); + return 1; + } + + LRESULT OnEraseBkgnd(HDC dc) { + return 1; + } + + void OnPaint(HDC ignore) { + PAINTSTRUCT ps; + HDC dc = ::BeginPaint(*this, &ps); + ::EndPaint(*this, &ps); + } + + void OnSize(int type, const CSize& sz) { + ::SetWindowPos(scrollbar_, + 0, + 0, + 0, + sz.cx, + sz.cy, + SWP_DEFERERASE | + SWP_NOACTIVATE | + SWP_NOCOPYBITS | + SWP_NOOWNERZORDER | + SWP_NOSENDCHANGING | + SWP_NOZORDER); + } + + void OnScroll(int code, HWND source, bool is_horizontal) { + int pos; + + if (code == SB_ENDSCROLL) { + return; + } + + // If we receive an event from the scrollbar, make the view + // component focused so we actually get mousewheel events. + if (source != NULL) { + ViewContainer* vc = parent_->GetViewContainer(); + if (vc && vc->GetHWND() != GetFocus()) { + parent_->RequestFocus(); + } + } + + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_POS | SIF_TRACKPOS; + ::GetScrollInfo(scrollbar_, SB_CTL, &si); + pos = si.nPos; + + ScrollBarController* sbc = parent_->GetController(); + + switch (code) { + case SB_BOTTOM: // case SB_RIGHT: + pos = parent_->GetMaxPosition(); + break; + case SB_TOP: // case SB_LEFT: + pos = parent_->GetMinPosition(); + break; + case SB_LINEDOWN: // case SB_LINERIGHT: + pos += sbc->GetScrollIncrement(parent_, false, true); + pos = std::min(parent_->GetMaxPosition(), pos); + break; + case SB_LINEUP: // case SB_LINELEFT: + pos -= sbc->GetScrollIncrement(parent_, false, false); + pos = std::max(parent_->GetMinPosition(), pos); + break; + case SB_PAGEDOWN: // case SB_PAGERIGHT: + pos += sbc->GetScrollIncrement(parent_, true, true); + pos = std::min(parent_->GetMaxPosition(), pos); + break; + case SB_PAGEUP: // case SB_PAGELEFT: + pos -= sbc->GetScrollIncrement(parent_, true, false); + pos = std::max(parent_->GetMinPosition(), pos); + break; + case SB_THUMBPOSITION: + case SB_THUMBTRACK: + pos = si.nTrackPos; + if (pos < parent_->GetMinPosition()) + pos = parent_->GetMinPosition(); + else if (pos > parent_->GetMaxPosition()) + pos = parent_->GetMaxPosition(); + break; + default: + break; + } + + sbc->ScrollToPosition(parent_, pos); + + si.nPos = pos; + si.fMask = SIF_POS; + ::SetScrollInfo(scrollbar_, SB_CTL, &si, TRUE); + + // Note: the system scrollbar modal loop doesn't give a chance + // to our message_loop so we need to call DidProcessMessage() + // manually. + // + // Sadly, we don't know what message has been processed. We may + // want to remove the message from DidProcessMessage() + MSG dummy; + dummy.hwnd = NULL; + dummy.message = 0; + MessageLoop::current()->DidProcessMessage(dummy); + } + + // note: always ignore 2nd param as it is 16 bits + void OnHorizScroll(int n_sb_code, int ignore, HWND source) { + OnScroll(n_sb_code, source, true); + } + + // note: always ignore 2nd param as it is 16 bits + void OnVertScroll(int n_sb_code, int ignore, HWND source) { + OnScroll(n_sb_code, source, false); + } + + + + ScrollBar* parent_; + HWND scrollbar_; +}; + +NativeScrollBar::NativeScrollBar(bool is_horiz) + : sb_view_(NULL), + sb_container_(NULL), + ScrollBar(is_horiz) { +} + +NativeScrollBar::~NativeScrollBar() { + if (sb_container_) { + // We always destroy the scrollbar container explicitly to cover all + // cases including when the container is no longer connected to a + // widget tree. + ::DestroyWindow(*sb_container_); + delete sb_container_; + } +} + +void NativeScrollBar::ViewHierarchyChanged(bool is_add, View *parent, + View *child) { + ViewContainer* vc; + if (is_add && (vc = GetViewContainer()) && !sb_view_) { + sb_view_ = new HWNDView(); + AddChildView(sb_view_); + sb_container_ = new ScrollBarContainer(this); + sb_view_->Attach(*sb_container_); + Layout(); + } +} + +void NativeScrollBar::Layout() { + if (sb_view_) { + CRect lb; + GetLocalBounds(&lb, true); + sb_view_->SetBounds(0, 0, lb.Width(), lb.Height()); + } +} + +void NativeScrollBar::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void NativeScrollBar::GetPreferredSize(CSize *out) { + DCHECK(out); + if (IsHorizontal()) { + out->cx = 0; + out->cy = GetLayoutSize(); + } else { + out->cx = GetLayoutSize(); + out->cy = 0; + } +} + +void NativeScrollBar::Update(int viewport_size, int content_size, int current_pos) { + ScrollBar::Update(viewport_size, content_size, current_pos); + if (!sb_container_) + return; + + if (content_size < 0) + content_size = 0; + + if (current_pos < 0) + current_pos = 0; + + if (current_pos > content_size) + current_pos = content_size; + + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_DISABLENOSCROLL | SIF_POS | SIF_RANGE | SIF_PAGE; + si.nMin = 0; + si.nMax = content_size; + si.nPos = current_pos; + si.nPage = viewport_size; + ::SetScrollInfo(sb_container_->GetScrollBarHWND(), + SB_CTL, + &si, + TRUE); +} + +int NativeScrollBar::GetLayoutSize() const { + return ::GetSystemMetrics(IsHorizontal() ? SM_CYHSCROLL : SM_CYVSCROLL); +} + +int NativeScrollBar::GetPosition() const { + SCROLLINFO si; + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + GetScrollInfo(sb_container_->GetScrollBarHWND(), SB_CTL, &si); + return si.nPos; +} + +bool NativeScrollBar::OnMouseWheel(const MouseWheelEvent& e) { + if (!sb_container_) { + return false; + } + + sb_container_->ScrollWithOffset(e.GetOffset()); + return true; +} + +bool NativeScrollBar::OnKeyPressed(const KeyEvent& event) { + if (!sb_container_) { + return false; + } + int code = -1; + switch(event.GetCharacter()) { + case VK_UP: + if (!IsHorizontal()) + code = SB_LINEUP; + break; + case VK_PRIOR: + code = SB_PAGEUP; + break; + case VK_NEXT: + code = SB_PAGEDOWN; + break; + case VK_DOWN: + if (!IsHorizontal()) + code = SB_LINEDOWN; + break; + case VK_HOME: + code = SB_TOP; + break; + case VK_END: + code = SB_BOTTOM; + break; + case VK_LEFT: + if (IsHorizontal()) + code = SB_LINELEFT; + break; + case VK_RIGHT: + if (IsHorizontal()) + code = SB_LINERIGHT; + break; + } + if (code != -1) { + ::SendMessage(*sb_container_, + IsHorizontal() ? WM_HSCROLL : WM_VSCROLL, + MAKELONG(static_cast<WORD>(code), 0), 0L); + return true; + } + return false; +} + +//static +int NativeScrollBar::GetHorizontalScrollBarHeight() { + return ::GetSystemMetrics(SM_CYHSCROLL); +} + +//static +int NativeScrollBar::GetVerticalScrollBarWidth() { + return ::GetSystemMetrics(SM_CXVSCROLL); +} + +} diff --git a/chrome/views/native_scroll_bar.h b/chrome/views/native_scroll_bar.h new file mode 100644 index 0000000..623b531 --- /dev/null +++ b/chrome/views/native_scroll_bar.h @@ -0,0 +1,88 @@ +// 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. + +#ifndef CHROME_VIEWS_NATIVE_SCROLLBAR_H__ +#define CHROME_VIEWS_NATIVE_SCROLLBAR_H__ + +#include "chrome/views/scroll_bar.h" + +namespace ChromeViews { + +class HWNDView; +class ScrollBarContainer; + +///////////////////////////////////////////////////////////////////////////// +// +// NativeScrollBar +// +// A View subclass that wraps a Native Windows scrollbar control. +// +// A scrollbar is either horizontal or vertical. +// +///////////////////////////////////////////////////////////////////////////// +class NativeScrollBar : public ScrollBar { + public: + + // Create new scrollbar, either horizontal or vertical + explicit NativeScrollBar(bool is_horiz); + virtual ~NativeScrollBar(); + + // Overridden for layout purpose + virtual void Layout(); + virtual void GetPreferredSize(CSize* sz); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Overridden for keyboard UI purpose + virtual bool OnKeyPressed(const KeyEvent& event); + virtual bool OnMouseWheel(const MouseWheelEvent& e); + + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Overridden from ScrollBar + virtual void Update(int viewport_size, int content_size, int current_pos); + virtual int GetLayoutSize() const; + virtual int GetPosition() const; + + // Return the system sizes + static int GetHorizontalScrollBarHeight(); + static int GetVerticalScrollBarWidth(); + + private: + // The sb_view_ takes care of keeping sb_container in sync with the + // view hierarchy + HWNDView* sb_view_; + + // sb_container_ is a custom hwnd that we use to wrap the real + // windows scrollbar. We need to do this to get the scroll events + // without having to do anything special in the high level hwnd. + ScrollBarContainer* sb_container_; +}; + +} +#endif diff --git a/chrome/views/painter.cc b/chrome/views/painter.cc new file mode 100644 index 0000000..a45d179 --- /dev/null +++ b/chrome/views/painter.cc @@ -0,0 +1,190 @@ +// 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 "chrome/views/painter.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/resource_bundle.h" +#include "skia/include/SkBitmap.h" +#include "skia/include/SkGradientShader.h" + +namespace ChromeViews { + +namespace { + +class GradientPainter : public Painter { + public: + GradientPainter(bool horizontal, const SkColor& top, const SkColor& bottom) + : horizontal_(horizontal) { + colors_[0] = top; + colors_[1] = bottom; + } + + virtual ~GradientPainter() { + } + + void Paint(int w, int h, ChromeCanvas* canvas) { + SkPaint paint; + SkPoint p[2]; + p[0].set(SkIntToScalar(0), SkIntToScalar(0)); + if (horizontal_) + p[1].set(SkIntToScalar(w), SkIntToScalar(0)); + else + p[1].set(SkIntToScalar(0), SkIntToScalar(h)); + + SkShader* s = + SkGradientShader::CreateLinear(p, colors_, NULL, 2, + SkShader::kClamp_TileMode, NULL); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(s); + // Need to unref shader, otherwise never deleted. + s->unref(); + + canvas->drawRectCoords(SkIntToScalar(0), SkIntToScalar(0), + SkIntToScalar(w), SkIntToScalar(h), paint); + } + + private: + bool horizontal_; + SkColor colors_[2]; + + DISALLOW_EVIL_CONSTRUCTORS(GradientPainter); +}; + + +} + +// static +void Painter::PaintPainterAt(int x, int y, int w, int h, + ChromeCanvas* canvas, Painter* painter) { + DCHECK(canvas && painter); + if (w < 0 || h < 0) + return; + canvas->save(); + canvas->TranslateInt(x, y); + painter->Paint(w, h, canvas); + canvas->restore(); +} + +ImagePainter::ImagePainter(const int image_resource_names[], + bool draw_center) + : draw_center_(draw_center) { + DCHECK(image_resource_names); + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0, max = draw_center ? 9 : 8; i < max; ++i) + images_.push_back(rb.GetBitmapNamed(image_resource_names[i])); +} + +void ImagePainter::Paint(int w, int h, ChromeCanvas* canvas) { + canvas->DrawBitmapInt(*images_[BORDER_TOP_LEFT], 0, 0); + canvas->TileImageInt(*images_[BORDER_TOP], + images_[BORDER_TOP_LEFT]->width(), + 0, + w - images_[BORDER_TOP_LEFT]->width() - + images_[BORDER_TOP_RIGHT]->width(), + images_[BORDER_TOP_LEFT]->height()); + canvas->DrawBitmapInt(*images_[BORDER_TOP_RIGHT], + w - images_[BORDER_TOP_RIGHT]->width(), + 0); + canvas->TileImageInt(*images_[BORDER_RIGHT], + w - images_[BORDER_RIGHT]->width(), + images_[BORDER_TOP_RIGHT]->height(), + images_[BORDER_RIGHT]->width(), + h - images_[BORDER_TOP_RIGHT]->height() - + images_[BORDER_BOTTOM_RIGHT]->height()); + canvas->DrawBitmapInt(*images_[BORDER_BOTTOM_RIGHT], + w - images_[BORDER_BOTTOM_RIGHT]->width(), + h - images_[BORDER_BOTTOM_RIGHT]->height()); + canvas->TileImageInt(*images_[BORDER_BOTTOM], + images_[BORDER_BOTTOM_LEFT]->width(), + h - images_[BORDER_BOTTOM]->height(), + w - images_[BORDER_BOTTOM_LEFT]->width() - + images_[BORDER_BOTTOM_RIGHT]->width(), + images_[BORDER_BOTTOM]->height()); + canvas->DrawBitmapInt(*images_[BORDER_BOTTOM_LEFT], + 0, + h - images_[BORDER_BOTTOM_LEFT]->height()); + canvas->TileImageInt(*images_[BORDER_LEFT], + 0, + images_[BORDER_TOP_LEFT]->height(), + images_[BORDER_LEFT]->width(), + h - images_[BORDER_TOP_LEFT]->height() - + images_[BORDER_BOTTOM_LEFT]->height()); + if (draw_center_) { + canvas->DrawBitmapInt(*images_[BORDER_CENTER], + 0, + 0, + images_[BORDER_CENTER]->width(), + images_[BORDER_CENTER]->height(), + images_[BORDER_TOP_LEFT]->width(), + images_[BORDER_TOP_LEFT]->height(), + w - images_[BORDER_TOP_LEFT]->width() - + images_[BORDER_TOP_RIGHT]->width(), + h - images_[BORDER_TOP_LEFT]->height() - + images_[BORDER_TOP_RIGHT]->height(), + false); + } +} + +// static +Painter* Painter::CreateHorizontalGradient(SkColor c1, SkColor c2) { + return new GradientPainter(true, c1, c2); +} + +// static +Painter* Painter::CreateVerticalGradient(SkColor c1, SkColor c2) { + return new GradientPainter(false, c1, c2); +} + +HorizontalPainter::HorizontalPainter(const int image_resource_names[]) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < 3; ++i) + images_[i] = rb.GetBitmapNamed(image_resource_names[i]); + height_ = images_[LEFT]->height(); + DCHECK(images_[LEFT]->height() == images_[RIGHT]->height() && + images_[LEFT]->height() == images_[CENTER]->height()); +} + +void HorizontalPainter::Paint(int w, int h, ChromeCanvas* canvas) { + if (w < (images_[LEFT]->width() + images_[CENTER]->width() + + images_[RIGHT]->width())) { + // No room to paint. + return; + } + canvas->DrawBitmapInt(*images_[LEFT], 0, 0); + canvas->DrawBitmapInt(*images_[RIGHT], w - images_[RIGHT]->width(), 0); + canvas->TileImageInt(*images_[CENTER], + images_[LEFT]->width(), + 0, + w - images_[LEFT]->width() - images_[RIGHT]->width(), + height_); +} + +} diff --git a/chrome/views/painter.h b/chrome/views/painter.h new file mode 100644 index 0000000..f293648 --- /dev/null +++ b/chrome/views/painter.h @@ -0,0 +1,145 @@ +// 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. + +#ifndef CHROME_VIEWS_PAINTER_H__ +#define CHROME_VIEWS_PAINTER_H__ + +#include <vector> + +#include "base/basictypes.h" +#include "skia/include/SkColor.h" + +class ChromeCanvas; +class SkBitmap; + +namespace ChromeViews { + +// Painter, as the name implies, is responsible for painting in a particular +// region. Think of Painter as a Border or Background that can be painted +// in any region of a View. +class Painter { + public: + // A convenience method for painting a Painter in a particular region. + // This translates the canvas to x/y and paints the painter. + static void PaintPainterAt(int x, int y, int w, int h, + ChromeCanvas* canvas, Painter* painter); + + // Creates a painter that draws a gradient between the two colors. + static Painter* CreateHorizontalGradient(SkColor c1, SkColor c2); + static Painter* CreateVerticalGradient(SkColor c1, SkColor c2); + + virtual ~Painter() {} + + // Paints the painter in the specified region. + virtual void Paint(int w, int h, ChromeCanvas* canvas) = 0; +}; + +// ImagePainter paints 8 (or 9) images into a box. The four corner +// images are drawn at the size of the image, the top/left/bottom/right +// images are tiled to fit the area, and the center (if rendered) is +// stretched. +class ImagePainter : public Painter { + public: + enum BorderElements { + BORDER_TOP_LEFT = 0, + BORDER_TOP, + BORDER_TOP_RIGHT, + BORDER_RIGHT, + BORDER_BOTTOM_RIGHT, + BORDER_BOTTOM, + BORDER_BOTTOM_LEFT, + BORDER_LEFT, + BORDER_CENTER + }; + + // Constructs a new ImagePainter loading the specified image names. + // The images must be in the order defined by the BorderElements. + // If draw_center is false, there must be 8 image names, if draw_center + // is true, there must be 9 image names with the last giving the name + // of the center image. + ImagePainter(const int image_resource_names[], + bool draw_center); + + virtual ~ImagePainter() {} + + // Paints the images. + virtual void Paint(int w, int h, ChromeCanvas* canvas); + + // Returns the specified image. The returned image should NOT be deleted. + SkBitmap* GetImage(BorderElements element) { + return images_[element]; + } + + private: + bool tile_; + bool draw_center_; + bool tile_center_; + // NOTE: the images are owned by ResourceBundle. Don't free them. + std::vector<SkBitmap*> images_; + + DISALLOW_EVIL_CONSTRUCTORS(ImagePainter); +}; + +// HorizontalPainter paints 3 images into a box: left, center and right. The left +// and right images are drawn to size at the left/right edges of the region. +// The center is tiled in the remaining space. All images must have the same +// height. +class HorizontalPainter : public Painter { + public: + // Constructs a new HorizontalPainter loading the specified image names. + // The images must be in the order left, right and center. + explicit HorizontalPainter(const int image_resource_names[]); + + virtual ~HorizontalPainter() {} + + // Paints the images. + virtual void Paint(int w, int h, ChromeCanvas* canvas); + + // Height of the images. + int GetHeight() const { return height_; } + + private: + // The image chunks. + enum BorderElements { + LEFT, + CENTER, + RIGHT + }; + + // The height. + int height_; + // NOTE: the images are owned by ResourceBundle. Don't free them. + SkBitmap* images_[3]; + + DISALLOW_EVIL_CONSTRUCTORS(HorizontalPainter); +}; + +} + +#endif // CHROME_VIEWS_PAINTER_H__ diff --git a/chrome/views/radio_button.cc b/chrome/views/radio_button.cc new file mode 100644 index 0000000..4f7c931 --- /dev/null +++ b/chrome/views/radio_button.cc @@ -0,0 +1,146 @@ +// 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 "chrome/views/radio_button.h" + +#include "chrome/views/label.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/root_view.h" + +namespace ChromeViews { + +// FIXME(ACW) there got be a better way to find out the check box sizes +static int kRadioWidth = 13; +static int kRadioHeight = 13; +static int kRadioToLabel = 4; + +const char RadioButton::kViewClassName[] = "chrome/views/RadioButton"; + +RadioButton::RadioButton(const std::wstring& label, int group_id) + : CheckBox(label) { + SetGroup(group_id); +} + +RadioButton::~RadioButton() { +} + +HWND RadioButton::CreateNativeControl(HWND parent_container) { + HWND r = ::CreateWindowEx(GetAdditionalExStyle(), + L"BUTTON", + L"", + WS_CHILD | BS_RADIOBUTTON , + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + ConfigureNativeButton(r); + return r; +} + +LRESULT RadioButton::OnCommand(UINT code, int id, HWND source) { + // Radio buttons can't be toggled off once selected except by clicking on + // another radio button within the same group, so we override this from + // CheckBox to prevent this from happening. + if (code == BN_CLICKED) { + RequestFocus(); + if (!IsSelected()) { + SetIsSelected(true); + return NativeButton::OnCommand(code, id, source); + } + } + return 0; +} + +// static +int RadioButton::GetTextIndent() { + return kRadioWidth + kRadioToLabel + kFocusPaddingHorizontal; +} + + +std::string RadioButton::GetClassName() const { + return kViewClassName; +} + +void RadioButton::GetPreferredSize(CSize *out) { + label_->GetPreferredSize(out); + out->cy = std::max(static_cast<int>(out->cy + kFocusPaddingVertical * 2), + kRadioHeight) ; + out->cx += kRadioToLabel + kRadioWidth + kFocusPaddingHorizontal * 2; +} + +void RadioButton::Layout() { + int label_x = GetTextIndent(); + label_->SetBounds(label_x, 0, GetWidth() - label_x, GetHeight()); + if (hwnd_view_) { + int first_line_height = label_->GetFont().height(); + hwnd_view_->SetBounds(0, ((first_line_height - kRadioHeight) / 2) + 1, + kRadioWidth, kRadioHeight); + hwnd_view_->UpdateHWNDBounds(); + } +} + +void RadioButton::SetIsSelected(bool f) { + if (f != IsSelected()) { + if (f) { + // We can't just get the root view here because sometimes the radio + // button isn't attached to a root view (e.g., if it's part of a tab page + // that is currently not active). + View* container = GetParent(); + while (container && container->GetParent()) + container = container->GetParent(); + if (container) { + std::vector<View*> other; + container->GetViewsWithGroup(GetGroup(), &other); + std::vector<View*>::iterator i; + for (i = other.begin(); i != other.end(); ++i) { + if (*i != this) { + RadioButton* peer = static_cast<RadioButton*>(*i); + peer->SetIsSelected(false); + } + } + } + } + CheckBox::SetIsSelected(f); + } +} + +View* RadioButton::GetSelectedViewForGroup(int group_id) { + std::vector<View*> views; + GetRootView()->GetViewsWithGroup(group_id, &views); + if (views.empty()) + return NULL; + + for (std::vector<View*>::const_iterator iter = views.begin(); + iter != views.end(); ++iter) { + RadioButton* radio_button = static_cast<RadioButton*>(*iter); + if (radio_button->IsSelected()) + return radio_button; + } + return NULL; +} + +} diff --git a/chrome/views/radio_button.h b/chrome/views/radio_button.h new file mode 100644 index 0000000..fe1ef74 --- /dev/null +++ b/chrome/views/radio_button.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef CHROME_VIEWS_RADIO_BUTTON_H__ +#define CHROME_VIEWS_RADIO_BUTTON_H__ + +#include "chrome/views/checkbox.h" + +namespace ChromeViews { + +//////////////////////////////////////////////////////////////////////////////// +// +// A wrapper for windows's native radio button. Radio buttons can be mutually +// exclusive with other radio buttons. +// +//////////////////////////////////////////////////////////////////////////////// +class RadioButton : public CheckBox { + public: + // The view class name. + static const char kViewClassName[]; + + // Create a radio button with the provided label and group id. + // The group id is used to identify all the other radio buttons which are in + // mutual exclusion with this radio button. Note: RadioButton assumes that + // all views with that group id are RadioButton. It is an error to give + // that group id to another view subclass which is not a radio button or + // a radio button subclass. + RadioButton(const std::wstring& label, int group_id); + virtual ~RadioButton(); + + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + virtual std::string GetClassName() const; + + // Overridden to properly perform mutual exclusion. + virtual void SetIsSelected(bool f); + + virtual View* GetSelectedViewForGroup(int group_id); + + // When focusing a RadioButton with Tab/Shift-Tab, only the selected button + // from the group should be accessible. + virtual bool IsGroupFocusTraversable() const { return false; } + + protected: + virtual HWND CreateNativeControl(HWND parent_container); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + private: + // Get the horizontal distance of the start of the text from the left of the + // control. + static int GetTextIndent(); + + DISALLOW_EVIL_CONSTRUCTORS(RadioButton); +}; + +} +#endif // CHROME_VIEWS_RADIO_BUTTON_H__ diff --git a/chrome/views/repeat_controller.cc b/chrome/views/repeat_controller.cc new file mode 100644 index 0000000..f17b3cf --- /dev/null +++ b/chrome/views/repeat_controller.cc @@ -0,0 +1,83 @@ +// 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 "chrome/views/repeat_controller.h" + +namespace ChromeViews { + +// The delay before the first and then subsequent repeats. Values taken from +// XUL code: http://mxr.mozilla.org/seamonkey/source/layout/xul/base/src/nsRepeatService.cpp#52 +static const int kInitialRepeatDelay = 250; +static const int kRepeatDelay = 50; + +/////////////////////////////////////////////////////////////////////////////// +// RepeatController, public: + +RepeatController::RepeatController(RepeatCallback* callback) + : timer_(NULL), + callback_(callback) { +} + +RepeatController::~RepeatController() { + DestroyTimer(); +} + +void RepeatController::Start() { + DCHECK(!timer_); + // The first timer is slightly longer than subsequent repeats. + timer_ = MessageLoop::current()->timer_manager()->StartTimer( + kInitialRepeatDelay, this, false); +} + +void RepeatController::Stop() { + DestroyTimer(); +} + +void RepeatController::Run() { + DestroyTimer(); + + // TODO(beng): (Cleanup) change this to just Run() when base rolls forward. + callback_->RunWithParams(Tuple0()); + timer_ = MessageLoop::current()->timer_manager()->StartTimer( + kRepeatDelay, this, true); +} + +/////////////////////////////////////////////////////////////////////////////// +// RepeatController, private: + +void RepeatController::DestroyTimer() { + if (!timer_) + return; + + MessageLoop::current()->timer_manager()->StopTimer(timer_); + delete timer_; + timer_ = NULL; +} + +} diff --git a/chrome/views/repeat_controller.h b/chrome/views/repeat_controller.h new file mode 100644 index 0000000..8beda1d --- /dev/null +++ b/chrome/views/repeat_controller.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef CHROME_VIEWS_REPEAT_CONTROLLER_H__ +#define CHROME_VIEWS_REPEAT_CONTROLLER_H__ + +#include "base/message_loop.h" +#include "base/task.h" + +namespace ChromeViews { + +/////////////////////////////////////////////////////////////////////////////// +// +// RepeatController +// +// An object that handles auto-repeating UI actions. There is a longer initial +// delay after which point repeats become constant. Users provide a callback +// that is notified when each repeat occurs so that they can perform the +// associated action. +// +/////////////////////////////////////////////////////////////////////////////// +class RepeatController : public Task { + public: + typedef Callback0::Type RepeatCallback; + + // The RepeatController takes ownership of this callback object. + explicit RepeatController(RepeatCallback* callback); + virtual ~RepeatController(); + + // Start repeating. + void Start(); + + // Stop repeating. + void Stop(); + + // Task implementation: + void Run(); + + private: + RepeatController(); + + // Stop and delete the timer. + void DestroyTimer(); + + // The current timer. + Timer* timer_; + + scoped_ptr<RepeatCallback> callback_; + + DISALLOW_EVIL_CONSTRUCTORS(RepeatController); +}; + +} + +#endif // #ifndef CHROME_VIEWS_REPEAT_CONTROLLER_H__ diff --git a/chrome/views/resize_corner.cc b/chrome/views/resize_corner.cc new file mode 100644 index 0000000..7c330d1 --- /dev/null +++ b/chrome/views/resize_corner.cc @@ -0,0 +1,57 @@ +// 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 "chrome/views/resize_corner.h" + +#include <vssym32.h> + +#include "base/gfx/native_theme.h" +#include "chrome/common/gfx/chrome_canvas.h" + +namespace ChromeViews { + +ResizeCorner::ResizeCorner() { +} + +ResizeCorner::~ResizeCorner() { +} + +void ResizeCorner::Paint(ChromeCanvas* canvas) { + // Paint the little handle. + RECT widgetRect = { 0, 0, GetWidth(), GetHeight() }; + HDC hdc = canvas->beginPlatformPaint(); + gfx::NativeTheme::instance()->PaintStatusGripper(hdc, + SP_GRIPPER, + 0, + 0, + &widgetRect); + canvas->endPlatformPaint(); +} + +} diff --git a/chrome/views/resize_corner.h b/chrome/views/resize_corner.h new file mode 100644 index 0000000..97889fd --- /dev/null +++ b/chrome/views/resize_corner.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef CHROME_VIEWS_RESIZE_CORNER_H__ +#define CHROME_VIEWS_RESIZE_CORNER_H__ + +#include "chrome/views/view.h" + +namespace ChromeViews { + +// Simple drawing of a resize corner. Has no functionality. +class ResizeCorner : public View { + public: + ResizeCorner(); + virtual ~ResizeCorner(); + + // View + virtual void Paint(ChromeCanvas* canvas); + + private: + DISALLOW_EVIL_CONSTRUCTORS(ResizeCorner); +}; + +} + +#endif // CHROME_VIEWS_RESIZE_CORNER_H__ diff --git a/chrome/views/root_view.cc b/chrome/views/root_view.cc new file mode 100644 index 0000000..f0ad017 --- /dev/null +++ b/chrome/views/root_view.cc @@ -0,0 +1,1003 @@ +// 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 "chrome/views/root_view.h" + +#include "base/base_drag_source.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/views/event.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/root_view_drop_target.h" +#include "chrome/views/view_container.h" + +#include "chrome/common/gfx/chrome_canvas.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// A Task to trigger non urgent painting. +// +///////////////////////////////////////////////////////////////////////////// +class PaintTask : public Task { + public: + explicit PaintTask(RootView* target) : root_view_(target) { + } + + ~PaintTask() {} + + void Cancel() { + root_view_ = NULL; + } + + void Run() { + if (root_view_) + root_view_->ProcessPendingPaint(); + } + private: + // The target root view. + RootView* root_view_; + + DISALLOW_EVIL_CONSTRUCTORS(PaintTask); +}; + +const char RootView::kViewClassName[] = "chrome/views/RootView"; + +///////////////////////////////////////////////////////////////////////////// +// +// RootView - constructors, destructors, initialization +// +///////////////////////////////////////////////////////////////////////////// + +RootView::RootView(ViewContainer* view_container, bool double_buffer) + : double_buffer_(double_buffer), + view_container_(view_container), + invalid_rect_(0,0,0,0), + mouse_pressed_handler_(NULL), + mouse_move_handler_(NULL), + explicit_mouse_handler_(FALSE), + previous_cursor_(NULL), + default_keyboard_hander_(NULL), + focus_on_mouse_pressed_(false), + ignore_set_focus_calls_(false), + focus_listener_(NULL), + focus_traversable_parent_(NULL), + focus_traversable_parent_view_(NULL), + invalid_rect_urgent_(false), + pending_paint_task_(NULL), + paint_task_needed_(false), + drag_view_(NULL) +#ifndef NDEBUG + , + is_processing_paint_(false) +#endif +{ +} + +RootView::~RootView() { + // If we have children remove them explicitly so to make sure a remove + // notification is sent for each one of them. + if (!child_views_.empty()) + RemoveAllChildViews(true); + + if (pending_paint_task_) + pending_paint_task_->Cancel(); // Ensure we're not called any more. +} + +///////////////////////////////////////////////////////////////////////////// +// +// RootView - layout, painting +// +///////////////////////////////////////////////////////////////////////////// + +void RootView::SchedulePaint(const CRect& r, bool urgent) { + // If there is an existing invalid rect, add the union of the scheduled + // rect with the invalid rect. This could be optimized further if + // necessary. + if (invalid_rect_.IsRectNull()) + invalid_rect_ = r; + else + invalid_rect_.UnionRect(invalid_rect_, r); + + if (urgent || invalid_rect_urgent_) { + invalid_rect_urgent_ = true; + } else { + if (!pending_paint_task_) { + pending_paint_task_ = new PaintTask(this); + MessageLoop::current()->PostTask(FROM_HERE, pending_paint_task_); + } + paint_task_needed_ = true; + } +} + +void RootView::SchedulePaint() { + View::SchedulePaint(); +} + +void RootView::SchedulePaint(int x, int y, int w, int h) { + View::SchedulePaint(); +} + +void RootView::ProcessPendingPaint() { + if (pending_paint_task_) { + pending_paint_task_->Cancel(); + pending_paint_task_ = NULL; + } + if (!paint_task_needed_) + return; + ViewContainer* vc = GetViewContainer(); + if (vc) + vc->PaintNow(invalid_rect_); +} + +#ifndef NDEBUG +// Sets the value of RootView's |is_processing_paint_| member to true as long +// as ProcessPaint is being called. Sets it to |false| when it returns. +class ScopedProcessingPaint { + public: + explicit ScopedProcessingPaint(bool* is_processing_paint) + : is_processing_paint_(is_processing_paint) { + *is_processing_paint_ = true; + } + + ~ScopedProcessingPaint() { + *is_processing_paint_ = false; + } + private: + bool* is_processing_paint_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedProcessingPaint); +}; +#endif + +void RootView::ProcessPaint(ChromeCanvas* canvas) { +#ifndef NDEBUG + ScopedProcessingPaint processing_paint(&is_processing_paint_); +#endif + + // Clip the invalid rect to our bounds. If a view is in a scrollview + // it could be a lot larger + invalid_rect_ = GetScheduledPaintRectConstrainedToSize(); + + if (invalid_rect_.IsRectNull()) + return; + + // Clear the background. + canvas->drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode); + + // Save the current transforms. + canvas->save(); + + // Set the clip rect according to the invalid rect. + int x = invalid_rect_.left + GetX(); + int y = invalid_rect_.top + GetY(); + canvas->ClipRectInt(x, y, invalid_rect_.Width(), invalid_rect_.Height()); + + // Paint the tree + View::ProcessPaint(canvas); + + // Restore the previous transform + canvas->restore(); + + ClearPaintRect(); +} + +void RootView::PaintNow() { + GetViewContainer()->PaintNow(invalid_rect_); +} + +bool RootView::NeedsPainting(bool urgent) { + bool has_invalid_rect = !invalid_rect_.IsRectNull(); + if (urgent) { + if (invalid_rect_urgent_) + return has_invalid_rect; + else + return false; + } else { + return has_invalid_rect; + } +} + +const CRect& RootView::GetScheduledPaintRect() { + return invalid_rect_; +} + +CRect RootView::GetScheduledPaintRectConstrainedToSize() { + if (invalid_rect_.IsRectEmpty()) + return invalid_rect_; + + CRect local_bounds; + GetLocalBounds(&local_bounds, true); + CRect invalid_rect; + invalid_rect.IntersectRect(&invalid_rect_, &local_bounds); + return invalid_rect; +} + +///////////////////////////////////////////////////////////////////////////// +// +// RootView - tree +// +///////////////////////////////////////////////////////////////////////////// + +ViewContainer* RootView::GetViewContainer() const { + return view_container_; +} + +///////////////////////////////////////////////////////////////////////////// +// +// RootView - event dispatch and propagation +// +///////////////////////////////////////////////////////////////////////////// + +void RootView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (!is_add) { + if (!explicit_mouse_handler_ && mouse_pressed_handler_ == child) { + mouse_pressed_handler_ = NULL; + } + + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(child); + + if (mouse_move_handler_ == child) { + mouse_move_handler_ = NULL; + } + + if (GetFocusedView() == child) { + FocusView(NULL); + } + + if (child == drag_view_) + drag_view_ = NULL; + + if (default_keyboard_hander_ == child) { + default_keyboard_hander_ = NULL; + } + NotificationService::current()-> + Notify(NOTIFY_VIEW_REMOVED, + Source<View>(child), Details<View>(parent)); + } +} + +void RootView::SetFocusOnMousePressed(bool f) { + focus_on_mouse_pressed_ = f; +} + +bool RootView::OnMousePressed(const MouseEvent& e) { + UpdateCursor(e); + + SetMouseLocationAndFlags(e); + + // If mouse_pressed_handler_ is non null, we are currently processing + // a pressed -> drag -> released session. In that case we send the + // event to mouse_pressed_handler_ + if (mouse_pressed_handler_) { + MouseEvent mouse_pressed_event(e, this, mouse_pressed_handler_); + drag_info.Reset(); + mouse_pressed_handler_->ProcessMousePressed(mouse_pressed_event, + &drag_info); + return true; + } + DCHECK(!explicit_mouse_handler_); + + bool hit_disabled_view = false; + // Walk up the tree until we find a view that wants the mouse event. + for (mouse_pressed_handler_ = GetViewForPoint(e.GetLocation()); + mouse_pressed_handler_ && (mouse_pressed_handler_ != this); + mouse_pressed_handler_ = mouse_pressed_handler_->GetParent()) { + if (!mouse_pressed_handler_->IsEnabled()) { + // Disabled views should eat events instead of propagating them upwards. + hit_disabled_view = true; + break; + } + + // See if this view wants to handle the mouse press. + const MouseEvent mouse_pressed_event(e, this, mouse_pressed_handler_); + drag_info.Reset(); + const bool handled = + mouse_pressed_handler_->ProcessMousePressed(mouse_pressed_event, + &drag_info); + + // The view could have removed itself from the tree when handling + // OnMousePressed(). In this case, the removal notification will have + // reset mouse_pressed_handler_ to NULL out from under us. Detect this + // case and stop. (See comments in view.h.) + // + // NOTE: Don't return true here, because we don't want the frame to + // forward future events to us when there's no handler. + if (!mouse_pressed_handler_) + break; + + // If the view handled the event, leave mouse_pressed_handler_ set and + // return true, which will cause subsequent drag/release events to get + // forwarded to that view. + if (handled) + return true; + } + + // Reset mouse_pressed_handler_ to indicate that no processing is occurring. + mouse_pressed_handler_ = NULL; + + if (focus_on_mouse_pressed_) { + HWND hwnd = view_container_->GetHWND(); + if (::GetFocus() != hwnd) { + ::SetFocus(hwnd); + } + } + return hit_disabled_view; +} + +bool RootView::ConvertPointToMouseHandler(const CPoint &l, CPoint *p) { + // + // If the mouse_handler was set explicitly, we need to keep + // sending events even if it was reparented in a different + // window. (a non explicit mouse handler is automatically + // cleared when the control is removed from the hierarchy) + if (explicit_mouse_handler_) { + if (mouse_pressed_handler_->GetViewContainer()) { + *p = l; + ConvertPointToScreen(this, p); + ConvertPointToView(NULL, mouse_pressed_handler_, p); + } else { + // If the mouse_pressed_handler_ is not connected, we send the + // event in screen coordinate system + *p = l; + ConvertPointToScreen(this, p); + return true; + } + } else { + *p = l; + ConvertPointToView(this, mouse_pressed_handler_, p); + } + return true; +} + +bool RootView::OnMouseDragged(const MouseEvent& e) { + UpdateCursor(e); + + if (mouse_pressed_handler_) { + SetMouseLocationAndFlags(e); + + CPoint p; + ConvertPointToMouseHandler(e.GetLocation(), &p); + MouseEvent mouse_event(e.GetType(), p.x, p.y, e.GetFlags()); + if (!mouse_pressed_handler_->ProcessMouseDragged(mouse_event, + &drag_info)) { + mouse_pressed_handler_ = NULL; + return false; + } else { + return true; + } + } + return false; +} + +void RootView::OnMouseReleased(const MouseEvent& e, bool canceled) { + UpdateCursor(e); + + if (mouse_pressed_handler_) { + CPoint p; + ConvertPointToMouseHandler(e.GetLocation(), &p); + MouseEvent mouse_released(e.GetType(), p.x, p.y, e.GetFlags()); + // We allow the view to delete us from ProcessMouseReleased. As such, + // configure state such that we're done first, then call View. + View* mouse_pressed_handler = mouse_pressed_handler_; + mouse_pressed_handler_ = NULL; + explicit_mouse_handler_ = false; + mouse_pressed_handler->ProcessMouseReleased(mouse_released, canceled); + // WARNING: we may have been deleted. + } +} + +void RootView::UpdateCursor(const MouseEvent& e) { + View *v = GetViewForPoint(e.GetLocation()); + + if (v && v != this) { + CPoint l(e.GetLocation()); + View::ConvertPointToView(this, v, &l); + HCURSOR cursor = v->GetCursorForPoint(e.GetType(), l.x, l.y); + if (cursor) { + ::SetCursor(cursor); + return; + } + } + if (previous_cursor_) { + SetCursor(previous_cursor_); + } +} + +void RootView::OnMouseMoved(const MouseEvent& e) { + View *v = GetViewForPoint(e.GetLocation()); + // Find the first enabled view. + while (v && !v->IsEnabled()) + v = v->GetParent(); + if (v && v != this) { + if (v != mouse_move_handler_) { + if (mouse_move_handler_ != NULL) { + MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0); + mouse_move_handler_->OnMouseExited(exited_event); + } + + mouse_move_handler_ = v; + + MouseEvent entered_event(Event::ET_MOUSE_ENTERED, + this, + mouse_move_handler_, + e.location(), + 0); + mouse_move_handler_->OnMouseEntered(entered_event); + } + MouseEvent moved_event(Event::ET_MOUSE_MOVED, + this, + mouse_move_handler_, + e.location(), + 0); + mouse_move_handler_->OnMouseMoved(moved_event); + + HCURSOR cursor = mouse_move_handler_->GetCursorForPoint( + moved_event.GetType(), moved_event.GetX(), moved_event.GetY()); + if (cursor) { + previous_cursor_ = ::SetCursor(cursor); + } else if (previous_cursor_) { + ::SetCursor(previous_cursor_); + previous_cursor_ = NULL; + } + } else if (mouse_move_handler_ != NULL) { + MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0); + mouse_move_handler_->OnMouseExited(exited_event); + if (previous_cursor_) { + ::SetCursor(previous_cursor_); + previous_cursor_ = NULL; + } + } +} + +void RootView::ProcessOnMouseExited() { + if (mouse_move_handler_ != NULL) { + MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0); + mouse_move_handler_->OnMouseExited(exited_event); + mouse_move_handler_ = NULL; + } +} + +void RootView::SetMouseHandler(View *new_mh) { + // If we're clearing the mouse handler, clear explicit_mouse_handler as well. + explicit_mouse_handler_ = (new_mh != NULL); + mouse_pressed_handler_ = new_mh; +} + +void RootView::OnViewContainerCreated() { + DCHECK(!drop_target_.get()); + drop_target_ = new RootViewDropTarget(this); +} + +void RootView::OnViewContainerDestroyed() { + if (drop_target_.get()) { + RevokeDragDrop(GetViewContainer()->GetHWND()); + drop_target_ = NULL; + } + view_container_ = NULL; +} + +void RootView::ProcessMouseDragCanceled() { + if (mouse_pressed_handler_) { + // Synthesize a release event. + MouseEvent release_event(Event::ET_MOUSE_RELEASED, last_mouse_event_x_, + last_mouse_event_y_, last_mouse_event_flags_); + OnMouseReleased(release_event, true); + } +} + +void RootView::SetFocusListener(FocusListener* listener) { + focus_listener_ = listener; +} + +void RootView::FocusView(View* view) { + if (view != GetFocusedView()) { + FocusManager* focus_manager = GetFocusManager(); + DCHECK(focus_manager) << "No Focus Manager for Window " << + (GetViewContainer() ? GetViewContainer()->GetHWND() : 0); + if (!focus_manager) + return; + + View* prev_focused_view = focus_manager->GetFocusedView(); + focus_manager->SetFocusedView(view); + + if (focus_listener_) + focus_listener_->FocusChanged(prev_focused_view, view); + } +} + +View* RootView::GetFocusedView() { + FocusManager* focus_manager = GetFocusManager(); + if (!focus_manager) { + // We may not have a FocusManager when the window that contains us is being + // deleted. Sadly we cannot wait for the window to be destroyed before we + // remove the FocusManager (see xp_frame.cc for more info). + return NULL; + } + + // Make sure the focused view belongs to this RootView's view hierarchy. + View* view = focus_manager->GetFocusedView(); + if (view && (view->GetRootView() == this)) + return view; + return NULL; +} + +View* RootView::FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + *focus_traversable = NULL; + *focus_traversable_view = NULL; + + if (GetChildViewCount() == 0) { + NOTREACHED(); + // Nothing to focus on here. + return NULL; + } + + bool skip_starting_view = true; + if (!starting_view) { + // Default to the first/last child + starting_view = reverse ? GetChildViewAt(GetChildViewCount() - 1) : + GetChildViewAt(0) ; + // If there was no starting view, then the one we select is a potential + // focus candidate. + skip_starting_view = false; + } else { + // The starting view should be part of this RootView. + DCHECK(IsParentOf(starting_view)); + } + + View* v = NULL; + if (!reverse) { + v = FindNextFocusableViewImpl(starting_view, skip_starting_view, + true, + (direction == DOWN) ? true : false, + starting_view->GetGroup()); + } else { + // If the starting view is focusable, we don't want to go down, as we are + // traversing the view hierarchy tree bottom-up. + bool can_go_down = (direction == DOWN) && !starting_view->IsFocusable(); + v = FindPreviousFocusableViewImpl(starting_view, true, + true, + can_go_down, + starting_view->GetGroup()); + } + if (v) { + if (v->IsFocusable()) + return v; + *focus_traversable = v->GetFocusTraversable(); + DCHECK(*focus_traversable); + *focus_traversable_view = v; + return NULL; + } + // Nothing found. + return NULL; +} + +// Strategy for finding the next focusable view: +// - keep going down the first child, stop when you find a focusable view or +// a focus traversable view (in that case return it) or when you reach a view +// with no children. +// - go to the right sibling and start the search from there (by invoking +// FindNextFocusableViewImpl on that view). +// - if the view has no right sibling, go up the parents until you find a parent +// with a right sibling and start the search from there. +View* RootView::FindNextFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id) { + if (!skip_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) + return FindSelectedViewForGroup(starting_view); + if (starting_view->GetFocusTraversable()) + return starting_view; + } + + // First let's try the left child. + if (can_go_down) { + View* v = NULL; + if (starting_view->GetChildViewCount() > 0) { + // We are only interested in non floating-views, as attached floating + // views order is variable (depending on mouse moves). + for (int i = 0; i < starting_view->GetChildViewCount(); i++) { + View* child = starting_view->GetChildViewAt(i); + if (!child->IsFloatingView()) { + v = FindNextFocusableViewImpl(child, false, false, true, + skip_group_id); + break; + } + } + } + if (v == NULL) { + // Try the floating views. + int id = 0; + if (starting_view->EnumerateFloatingViews(View::FIRST, 0, &id)) { + View* child = starting_view->RetrieveFloatingViewForID(id); + DCHECK(child); + v = FindNextFocusableViewImpl(child, false, false, true, skip_group_id); + } + } + if (v) + return v; + } + + // Then try the right sibling. + View* sibling = NULL; + if (starting_view->IsFloatingView()) { + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews( + View::NEXT, starting_view->GetFloatingViewID(), &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + } else { + sibling = starting_view->GetNextFocusableView(); + if (!sibling) { + // Let's try floating views. + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews(View::FIRST, + 0, &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + } + } + if (sibling) { + View* v = + FindNextFocusableViewImpl(sibling, false, false, true, skip_group_id); + if (v) + return v; + } + + // Then go up to the parent sibling. + if (can_go_up) { + View* parent = starting_view->GetParent(); + while (parent) { + int id = 0; + if (parent->IsFloatingView() && + parent->GetParent()->EnumerateFloatingViews( + View::NEXT, parent->GetFloatingViewID(), &id)) { + sibling = parent->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } else { + sibling = parent->GetNextFocusableView(); + } + if (sibling) { + return FindNextFocusableViewImpl(sibling, + false, true, true, + skip_group_id); + } + parent = parent->GetParent(); + } + } + + // We found nothing. + return NULL; +} + +// Strategy for finding the previous focusable view: +// - keep going down on the right until you reach a view with no children, if it +// it is a good candidate return it. +// - start the search on the left sibling. +// - if there are no left sibling, start the search on the parent (without going +// down). +View* RootView::FindPreviousFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id) { + // Let's go down and right as much as we can. + if (can_go_down) { + View* v = NULL; + if (starting_view->GetChildViewCount() - + starting_view->GetFloatingViewCount() > 0) { + View* view = + starting_view->GetChildViewAt(starting_view->GetChildViewCount() - 1); + v = FindPreviousFocusableViewImpl(view, false, false, true, + skip_group_id); + } else { + // Let's try floating views. + int id = 0; + if (starting_view->EnumerateFloatingViews(View::LAST, 0, &id)) { + View* child = starting_view->RetrieveFloatingViewForID(id); + DCHECK(child); + v = FindNextFocusableViewImpl(child, false, false, true, skip_group_id); + } + } + if (v) + return v; + } + + if (!skip_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) + return FindSelectedViewForGroup(starting_view); + if (starting_view->GetFocusTraversable()) + return starting_view; + } + + // Then try the left sibling. + View* sibling = NULL; + if (starting_view->IsFloatingView()) { + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews( + View::PREVIOUS, starting_view->GetFloatingViewID(), &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + if (!sibling) { + // No more floating views, try regular views, starting at the last one. + View* parent = starting_view->GetParent(); + for (int i = parent->GetChildViewCount() - 1; i >= 0; i--) { + View* v = parent->GetChildViewAt(i); + if (!v->IsFloatingView()) { + sibling = v; + break; + } + } + } + } else { + sibling = starting_view->GetPreviousFocusableView(); + } + if (sibling) { + return FindPreviousFocusableViewImpl(sibling, + false, true, true, + skip_group_id); + } + + // Then go up the parent. + if (can_go_up) { + View* parent = starting_view->GetParent(); + if (parent) + return FindPreviousFocusableViewImpl(parent, + false, true, false, + skip_group_id); + } + + // We found nothing. + return NULL; +} + +FocusTraversable* RootView::GetFocusTraversableParent() { + return focus_traversable_parent_; +} + +void RootView::SetFocusTraversableParent(FocusTraversable* focus_traversable) { + DCHECK(focus_traversable != this); + focus_traversable_parent_ = focus_traversable; +} + +View* RootView::GetFocusTraversableParentView() { + return focus_traversable_parent_view_; +} + +void RootView::SetFocusTraversableParentView(View* view) { + focus_traversable_parent_view_ = view; +} + +// static +View* RootView::FindSelectedViewForGroup(View* view) { + if (view->IsGroupFocusTraversable() || + view->GetGroup() == -1) // No group for that view. + return view; + + View* selected_view = view->GetSelectedViewForGroup(view->GetGroup()); + if (selected_view) + return selected_view; + + // No view selected for that group, default to the specified view. + return view; +} + +// static +bool RootView::IsViewFocusableCandidate(View* v, int skip_group_id) { + return v->IsFocusable() && + (v->IsGroupFocusTraversable() || skip_group_id == -1 || + v->GetGroup() != skip_group_id); +} + +void RootView::ProcessKeyEvent(const KeyEvent& event) { + View* v; + bool consumed = false; + if (GetFocusedView()) { + for (v = GetFocusedView(); + v && v != this && !consumed; v = v->GetParent()) { + if (event.GetType() == Event::ET_KEY_PRESSED) + consumed = v->OnKeyPressed(event); + else + consumed = v->OnKeyReleased(event); + } + } + + if (!consumed && default_keyboard_hander_) { + if (event.GetType() == Event::ET_KEY_PRESSED) + default_keyboard_hander_->OnKeyPressed(event); + else + default_keyboard_hander_->OnKeyReleased(event); + } +} + +bool RootView::ProcessMouseWheelEvent(const MouseWheelEvent& e) { + View* v; + bool consumed = false; + if (GetFocusedView()) { + for (v = GetFocusedView(); + v && v != this && !consumed; v = v->GetParent()) { + consumed = v->OnMouseWheel(e); + } + } + + if (!consumed && default_keyboard_hander_) { + consumed = default_keyboard_hander_->OnMouseWheel(e); + } + return consumed; +} + +void RootView::SetDefaultKeyboardHandler(View* v) { + default_keyboard_hander_ = v; +} + +bool RootView::IsVisibleInRootView() const { + return IsVisible(); +} + +void RootView::ViewBoundsChanged(View* view, bool size_changed, + bool position_changed) { + DCHECK(view && (size_changed || position_changed)); + if (!view->descendants_to_notify_.get()) + return; + + for (std::vector<View*>::iterator i = view->descendants_to_notify_->begin(); + i != view->descendants_to_notify_->end(); ++i) { + (*i)->VisibleBoundsInRootChanged(); + } +} + +void RootView::RegisterViewForVisibleBoundsNotification(View* view) { + DCHECK(view); + if (view->registered_for_visible_bounds_notification_) + return; + view->registered_for_visible_bounds_notification_ = true; + View* ancestor = view->GetParent(); + while (ancestor) { + ancestor->AddDescendantToNotify(view); + ancestor = ancestor->GetParent(); + } +} + +void RootView::UnregisterViewForVisibleBoundsNotification(View* view) { + DCHECK(view); + if (!view->registered_for_visible_bounds_notification_) + return; + view->registered_for_visible_bounds_notification_ = false; + View* ancestor = view->GetParent(); + while (ancestor) { + ancestor->RemoveDescendantToNotify(view); + ancestor = ancestor->GetParent(); + } +} + +void RootView::SetMouseLocationAndFlags(const MouseEvent& e) { + last_mouse_event_flags_ = e.GetFlags(); + last_mouse_event_x_ = e.GetX(); + last_mouse_event_y_ = e.GetY(); +} + +std::string RootView::GetClassName() const { + return kViewClassName; +} + +void RootView::ClearPaintRect() { + invalid_rect_.SetRectEmpty(); + + // This painting has been done. Reset the urgent flag. + invalid_rect_urgent_ = false; + + // If a pending_paint_task_ does Run(), we don't need to do anything. + paint_task_needed_ = false; +} + +void RootView::OnPaint(HWND hwnd) { + CRect original_dirty_region = + GetScheduledPaintRectConstrainedToSize(); + if (!original_dirty_region.IsRectEmpty()) { + // Invoke InvalidateRect so that the dirty region of the window includes the + // region we need to paint. If we didn't do this and the region didn't + // include the dirty region, ProcessPaint would incorrectly mark everything + // as clean. This can happen if a WM_PAINT is generated by the system before + // the InvokeLater schedule by RootView is processed. + InvalidateRect(hwnd, &original_dirty_region, FALSE); + } + ChromeCanvasPaint canvas(hwnd); + if (!canvas.isEmpty()) { + const PAINTSTRUCT& ps = canvas.paintStruct(); + SchedulePaint(ps.rcPaint, false); + if (NeedsPainting(false)) + ProcessPaint(&canvas); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// RootView - accessibility +// +///////////////////////////////////////////////////////////////////////////// + +bool RootView::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_APPLICATION; + return true; +} + +bool RootView::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void RootView::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +void RootView::StartDragForViewFromMouseEvent( + View* view, + IDataObject* data, + int operation) { + drag_view_ = view; + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + DoDragDrop(data, drag_source, + DragDropTypes::DragOperationToDropEffect(operation), &effects); + // If the view is removed during the drag operation, drag_view_ is set to + // NULL. + if (drag_view_ == view) { + View* drag_view = drag_view_; + drag_view_ = NULL; + drag_view->OnDragDone(); + } +} + +View* RootView::GetDragView() { + return drag_view_; +} + +} diff --git a/chrome/views/root_view.h b/chrome/views/root_view.h new file mode 100644 index 0000000..545f553 --- /dev/null +++ b/chrome/views/root_view.h @@ -0,0 +1,373 @@ +// 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. + +#ifndef CHROME_VIEWS_ROOT_VIEW_H__ +#define CHROME_VIEWS_ROOT_VIEW_H__ + +#include "base/ref_counted.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class ViewContainer; +class PaintTask; +class RootViewDropTarget; + +//////////////////////////////////////////////////////////////////////////////// +// +// FocusListener Interface +// +//////////////////////////////////////////////////////////////////////////////// +class FocusListener { + public: + virtual void FocusChanged(View* lost_focus, View* got_focus) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// RootView class +// +// The RootView is the root of a View hierarchy. Its parent is not +// necessarily a ViewContainer, but the ViewContainer's View child is +// always a RootView. +// +// The RootView manages the View hierarchy's interface with the +// ViewContainer, and also maintains the current invalid rect - the region +// that needs repainting. +// +///////////////////////////////////////////////////////////////////////////// +class RootView : public View, + public FocusTraversable { + public: + static const char kViewClassName[]; + + RootView(ViewContainer* view_container, bool double_buffer); + + virtual ~RootView(); + + // Layout and Painting functions + + // Overridden from View to implement paint scheduling. + virtual void SchedulePaint(const CRect& r, bool urgent); + + // Convenience to schedule the whole view + virtual void SchedulePaint(); + + // Convenience to schedule a paint given some ints + virtual void SchedulePaint(int x, int y, int w, int h); + + // Paint this RootView and its child Views. + virtual void ProcessPaint(ChromeCanvas* canvas); + + // Paint this View's invalid rect immediately. + virtual void PaintNow(); + + // Whether or not this View needs repainting. If |urgent| is true, this method + // returns whether this root view needs to paint as soon as possible. + virtual bool NeedsPainting(bool urgent); + + // Invoked by the ViewContainer to discover what rectangle should be + // painted + const CRect& GetScheduledPaintRect(); + + // Returns the region scheduled to paint clipped to the RootViews bounds. + CRect GetScheduledPaintRectConstrainedToSize(); + + // Tree functions + + // Get the ViewContainer that hosts this View. + virtual ViewContainer* GetViewContainer() const; + + // The following event methods are overridden to propagate event to the + // control tree + virtual bool OnMousePressed(const MouseEvent& e); + virtual bool OnMouseDragged(const MouseEvent& e); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual void OnMouseMoved(const MouseEvent& e); + virtual void SetMouseHandler(View* new_mouse_handler); + + // Invoked when the ViewContainers has been fully initialized. + // At the time the constructor is invoked the ViewContainer may not be + // completely initialized, when this method is invoked, it is. + void OnViewContainerCreated(); + + // Invoked prior to the ViewContainer being destroyed. + void OnViewContainerDestroyed(); + + // Invoked By the ViewContainer if the mouse drag is interrupted by + // the system. Invokes OnMouseReleased with a value of true for canceled. + void ProcessMouseDragCanceled(); + + // Invoked by the ViewContainer instance when the mouse moves outside of + // the container bounds + virtual void ProcessOnMouseExited(); + + // Make the provided view focused. Also make sure that our container + // is focused. + void FocusView(View* view); + + // Check whether the provided view is in the focus path. The focus path is the + // path between the focused view (included) to the root view. + bool IsInFocusPath(View* view); + + // Returns the View in this RootView hierarchy that has the focus, or NULL if + // no View currently has the focus. + View* GetFocusedView(); + + // Process a key event. Send the event to the focused view and up + // the focus path until the event is consumed. + virtual void ProcessKeyEvent(const KeyEvent& event); + + // Set the default keyboard handler. The default keyboard handler is + // a view that will get an opportunity to process key events when all + // views in the focus path did not process an event. + // + // Note: this is a single view at this point. We may want to make + // this a list if needed. + void SetDefaultKeyboardHandler(View* v); + + // Set whether this root view should focus the corresponding hwnd + // when an unprocessed mouse event occurs. + void SetFocusOnMousePressed(bool f); + + // Process a mousewheel event. Return true if the event was processed + // and false otherwise. + // MouseWheel events are sent on the focus path. + virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e); + + // Overridden to handle special root view case. + virtual bool IsVisibleInRootView() const; + + // Sets a listener that receives focus changes events. + void SetFocusListener(FocusListener* listener); + + // FocusTraversable implementation. + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + virtual FocusTraversable* GetFocusTraversableParent(); + virtual View* GetFocusTraversableParentView(); + + // Used to set the FocusTraversable parent after the view has been created + // (typically when the hierarchy changes and this RootView is added/removed). + virtual void SetFocusTraversableParent(FocusTraversable* focus_traversable); + + // Used to set the View parent after the view has been created. + virtual void SetFocusTraversableParentView(View* view); + + // Returns the name of this class: chrome/views/RootView + virtual std::string GetClassName() const; + + // Clears the region that is schedule to be painted. You nearly never need + // to invoke this. This is primarily intended for ViewContainers. + void ClearPaintRect(); + + // Invoked from the ViewContainer to service a WM_PAINT call. + void OnPaint(HWND hwnd); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Assigns an accessible string name. + void SetAccessibleName(const std::wstring& name); + + protected: + + // Overridden to properly reset our event propagation member + // variables when a child is removed + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + +#ifndef NDEBUG + virtual bool IsProcessingPaint() const { return is_processing_paint_; } +#endif + + private: + friend class View; + friend class PaintTask; + + RootView(); + DISALLOW_EVIL_CONSTRUCTORS(RootView); + + // Convert a point to our current mouse handler. Returns false if the + // mouse handler is not connected to a ViewContainer. In that case, the + // conversion cannot take place and *p is unchanged + bool ConvertPointToMouseHandler(const CPoint &l, CPoint *p); + + // Update the cursor given a mouse event. This is called by non mouse_move + // event handlers to honor the cursor desired by views located under the + // cursor during drag operations. + void UpdateCursor(const MouseEvent& e); + + // Notification that size and/or position of a view has changed. This + // notifies the appropriate views. + void ViewBoundsChanged(View* view, bool size_changed, bool position_changed); + + // Registers a view for notification when the visible bounds relative to the + // root of a view changes. + void RegisterViewForVisibleBoundsNotification(View* view); + void UnregisterViewForVisibleBoundsNotification(View* view); + + // Invoked by PaintTask to paint the root view in a non urgent way. + void ProcessPendingPaint(); + + // Returns the next focusable view or view containing a FocusTraversable (NULL + // if none was found), starting at the starting_view. + // skip_starting_view, can_go_up and can_go_down controls the traversal of + // the views hierarchy. + // skip_group_id specifies a group_id, -1 means no group. All views from a + // group are traversed in one pass. + View* FindNextFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id); + + // Same as FindNextFocusableViewImpl but returns the previous focusable view. + View* FindPreviousFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id); + + // Convenience method that returns true if a view is focusable and does not + // belong to the specified group. + bool IsViewFocusableCandidate(View* v, int skip_group_id); + + // Returns the view selected for the group of the selected view. If the view + // does not belong to a group or if no view is selected in the group, the + // specified view is returned. + static View* FindSelectedViewForGroup(View* view); + + // Updates the last_mouse_* fields from e. + void SetMouseLocationAndFlags(const MouseEvent& e); + + // Starts a drag operation for the specified view. This blocks until done. + // If the view has not been deleted during the drag, OnDragDone is invoked + // on the view. + void StartDragForViewFromMouseEvent(View* view, + IDataObject* data, + int operation); + + // If a view is dragging, this returns it. Otherwise returns NULL. + View* GetDragView(); + + // Whether or not we're double buffering paints + bool double_buffer_; + + // The view currently handing down - drag - up + View* mouse_pressed_handler_; + + // The view currently handling enter / exit + View* mouse_move_handler_; + + // The host ViewContainer + ViewContainer* view_container_; + + // The rectangle that should be painted + CRect invalid_rect_; + + // Whether the current invalid rect should be painted urgently. + bool invalid_rect_urgent_; + + // The task that we are using to trigger some non urgent painting or NULL + // if no painting has been scheduled yet. + PaintTask* pending_paint_task_; + + // Indicate if, when the pending_paint_task_ is run, actual painting is still + // required. + bool paint_task_needed_; + + // true if mouse_handler_ has been explicitly set + bool explicit_mouse_handler_; + + // Previous cursor + HCURSOR previous_cursor_; + + // Default keyboard handler + View* default_keyboard_hander_; + + // The listener that gets focus change notifications. + FocusListener* focus_listener_; + + // Whether this root view should make our hwnd focused + // when an unprocessed mouse press event occurs + bool focus_on_mouse_pressed_; + + // Flag used to ignore focus events when we focus the native window associated + // with a view. + bool ignore_set_focus_calls_; + + // Whether this root view belongs to the current active window. + // bool activated_; + + // Last position/flag of a mouse press/drag. Used if capture stops and we need + // to synthesize a release. + int last_mouse_event_flags_; + int last_mouse_event_x_; + int last_mouse_event_y_; + + // The parent FocusTraversable, used for focus traversal. + FocusTraversable* focus_traversable_parent_; + + // The View that contains this RootView. This is used when we have RootView + // wrapped inside native components, and is used for the focus traversal. + View* focus_traversable_parent_view_; + + // Handles dnd for us. + scoped_refptr<RootViewDropTarget> drop_target_; + + // Storage of strings needed for accessibility. + std::wstring accessible_name_; + + // Tracks drag state for a view. + View::DragInfo drag_info; + + // Valid for the lifetime of StartDragForViewFromMouseEvent, indicates the + // view the drag started from. + View* drag_view_; + +#ifndef NDEBUG + // True if we're currently processing paint. + bool is_processing_paint_; +#endif +}; + +} + +#endif // CHROME_VIEWS_ROOT_VIEW_H__ diff --git a/chrome/views/root_view_drop_target.cc b/chrome/views/root_view_drop_target.cc new file mode 100644 index 0000000..bad4e07 --- /dev/null +++ b/chrome/views/root_view_drop_target.cc @@ -0,0 +1,142 @@ +// 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 "chrome/views/root_view_drop_target.h" + +#include "base/logging.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/views/root_view.h" + +namespace ChromeViews { + +RootViewDropTarget::RootViewDropTarget(RootView* root_view) + : BaseDropTarget(root_view->GetViewContainer()->GetHWND()), + root_view_(root_view), + target_view_(NULL), + deepest_view_(NULL) { +} + +RootViewDropTarget::~RootViewDropTarget() { +} + +void RootViewDropTarget::ResetTargetViewIfEquals(View* view) { + if (target_view_ == view) + target_view_ = NULL; + if (deepest_view_ == view) + deepest_view_ = NULL; +} + +DWORD RootViewDropTarget::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + const OSExchangeData data(data_object); + CPoint root_view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, root_view_, &root_view_location); + View* view = CalculateTargetView(root_view_location, data); + + if (view != target_view_) { + // Target changed notify old drag exited, then new drag entered. + if (target_view_) + target_view_->OnDragExited(); + target_view_ = view; + if (target_view_) { + CPoint target_view_location(root_view_location.x, root_view_location.y); + View::ConvertPointToView(root_view_, target_view_, &target_view_location); + DropTargetEvent enter_event(data, + target_view_location.x, + target_view_location.y, + DragDropTypes::DropEffectToDragOperation(effect)); + target_view_->OnDragEntered(enter_event); + } + } + + if (target_view_) { + CPoint target_view_location(root_view_location.x, root_view_location.y); + View::ConvertPointToView(root_view_, target_view_, &target_view_location); + DropTargetEvent enter_event(data, + target_view_location.x, + target_view_location.y, + DragDropTypes::DropEffectToDragOperation(effect)); + int result_operation = target_view_->OnDragUpdated(enter_event); + return DragDropTypes::DragOperationToDropEffect(result_operation); + } else { + return DROPEFFECT_NONE; + } +} + +void RootViewDropTarget::OnDragLeave(IDataObject* data_object) { + if (target_view_) + target_view_->OnDragExited(); + deepest_view_ = target_view_ = NULL; +} + +DWORD RootViewDropTarget::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + const OSExchangeData data(data_object); + DWORD drop_effect = OnDragOver(data_object, key_state, cursor_position, + effect); + View* drop_view = target_view_; + deepest_view_ = target_view_ = NULL; + if (drop_effect != DROPEFFECT_NONE) { + CPoint view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, drop_view, &view_location); + DropTargetEvent drop_event(data, view_location.x, view_location.y, + DragDropTypes::DropEffectToDragOperation(effect)); + return DragDropTypes::DragOperationToDropEffect( + drop_view->OnPerformDrop(drop_event)); + } else { + if (drop_view) + drop_view->OnDragExited(); + return DROPEFFECT_NONE; + } +} + +View* RootViewDropTarget::CalculateTargetView( + const CPoint& root_view_location, + const OSExchangeData& data) { + View* view = root_view_->GetViewForPoint(root_view_location); + if (view == deepest_view_) { + // The view the mouse is over hasn't changed; reuse the target. + return target_view_; + } + // View under mouse changed, which means a new view may want the drop. + // Walk the tree, stopping at target_view_ as we know it'll accept the + // drop. + deepest_view_ = view; + while (view && view != target_view_ && + (!view->IsEnabled() || !view->CanDrop(data))) { + view = view->GetParent(); + } + return view; +} + +} // namespace diff --git a/chrome/views/root_view_drop_target.h b/chrome/views/root_view_drop_target.h new file mode 100644 index 0000000..ad92e1d --- /dev/null +++ b/chrome/views/root_view_drop_target.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef CHROME_VIEWS_ROOT_VIEW_DROP_TARGET_H__ +#define CHROME_VIEWS_ROOT_VIEW_DROP_TARGET_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> + +#include "base/base_drop_target.h" +#include "chrome/common/os_exchange_data.h" + +namespace ChromeViews { + +class RootView; +class View; + +// RootViewDropTarget takes care of managing drag and drop for the RootView and +// converts Windows OLE drop messages into Views drop messages. +// +// RootViewDropTarget is responsible for determining the appropriate View to +// use during a drag and drop session, and forwarding events to it. +class RootViewDropTarget : public BaseDropTarget { + public: + explicit RootViewDropTarget(RootView* root_view); + virtual ~RootViewDropTarget(); + + // If a drag and drop is underway and view is the current drop target, the + // drop target is set to null. + // This is invoked when a View is removed from the RootView to make sure + // we don't target a view that was removed during dnd. + void ResetTargetViewIfEquals(View* view); + + protected: + virtual DWORD OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + virtual void OnDragLeave(IDataObject* data_object); + + virtual DWORD OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + private: + // Calculates the target view for a drop given the specified location in + // the coordinate system of the rootview. This tries to avoid continually + // querying CanDrop by returning target_view_ if the mouse is still over + // target_view_. + View* CalculateTargetView(const CPoint& root_view_location, + const OSExchangeData& data); + + // RootView we were created for. + RootView* root_view_; + + // View we're targeting events at. + View* target_view_; + + // The deepest view under the current drop coordinate. + View* deepest_view_; + + DISALLOW_EVIL_CONSTRUCTORS(RootViewDropTarget); +}; + +} // namespace + +#endif // CHROME_VIEWS_ROOT_VIEW_DROP_TARGET_H__ diff --git a/chrome/views/scroll_bar.cc b/chrome/views/scroll_bar.cc new file mode 100644 index 0000000..2549ad3 --- /dev/null +++ b/chrome/views/scroll_bar.cc @@ -0,0 +1,72 @@ +// 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 "chrome/views/scroll_bar.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// ScrollBar implementation +// +///////////////////////////////////////////////////////////////////////////// + +ScrollBar::ScrollBar(bool is_horiz) : is_horiz_(is_horiz), + controller_(NULL), + max_pos_(0) { +} + +ScrollBar::~ScrollBar() { +} + +bool ScrollBar::IsHorizontal() const { + return is_horiz_; +} + +void ScrollBar::SetController(ScrollBarController* controller) { + controller_ = controller; +} + +ScrollBarController* ScrollBar::GetController() const { + return controller_; +} + +void ScrollBar::Update(int viewport_size, int content_size, int current_pos) { + max_pos_ = std::max(0, content_size - viewport_size); +} + +int ScrollBar::GetMaxPosition() const { + return max_pos_; +} + +int ScrollBar::GetMinPosition() const { + return 0; +} + +} diff --git a/chrome/views/scroll_bar.h b/chrome/views/scroll_bar.h new file mode 100644 index 0000000..fb3494a --- /dev/null +++ b/chrome/views/scroll_bar.h @@ -0,0 +1,126 @@ +// 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. + +#ifndef CHROME_VIEWS_SCROLLBAR_H__ +#define CHROME_VIEWS_SCROLLBAR_H__ + +#include "chrome/views/view.h" +#include "chrome/views/event.h" + +namespace ChromeViews { + +class ScrollBar; + +///////////////////////////////////////////////////////////////////////////// +// +// ScrollBarController +// +// ScrollBarController defines the method that should be implemented to +// receive notification from a scrollbar +// +///////////////////////////////////////////////////////////////////////////// +class ScrollBarController { + public: + + // Invoked by the scrollbar when the scrolling position changes + // This method typically implements the actual scrolling. + // + // The provided position is expressed in pixels. It is the new X or Y + // position which is in the GetMinPosition() / GetMaxPosition range. + virtual void ScrollToPosition(ScrollBar* source, int position) = 0; + + // Returns the amount to scroll. The amount to scroll may be requested in + // two different amounts. If is_page is true the 'page scroll' amount is + // requested. The page scroll amount typically corresponds to the + // visual size of the view. If is_page is false, the 'line scroll' amount + // is being requested. The line scroll amount typically corresponds to the + // size of one row/column. + // + // The return value should always be positive. A value <= 0 results in + // scrolling by a fixed amount. + virtual int GetScrollIncrement(ScrollBar* source, + bool is_page, + bool is_positive) = 0; +}; + +///////////////////////////////////////////////////////////////////////////// +// +// ScrollBar +// +// A View subclass to wrap to implement a ScrollBar. Our current windows +// version simply wraps a native windows scrollbar. +// +// A scrollbar is either horizontal or vertical +// +///////////////////////////////////////////////////////////////////////////// +class ScrollBar : public View { + public: + virtual ~ScrollBar(); + + // Return whether this scrollbar is horizontal + bool IsHorizontal() const; + + // Set / Get the controller + void SetController(ScrollBarController* controller); + ScrollBarController* GetController() const; + + // Update the scrollbar appearance given a viewport size, content size and + // current position + virtual void Update(int viewport_size, int content_size, int current_pos); + + // Return the max and min positions + int GetMaxPosition() const; + int GetMinPosition() const; + + // Returns the position of the scrollbar. + virtual int GetPosition() const = 0; + + // Get the width or height of this scrollbar, for use in layout calculations. + // For a vertical scrollbar, this is the width of the scrollbar, likewise it + // is the height for a horizontal scrollbar. + virtual int GetLayoutSize() const = 0; + + protected: + // Create new scrollbar, either horizontal or vertical. These are protected + // since you need to be creating either a NativeScrollBar or a + // BitmapScrollBar. + ScrollBar(bool is_horiz); + + private: + const bool is_horiz_; + + // Current controller + ScrollBarController* controller_; + + // properties + int max_pos_; +}; + +} +#endif diff --git a/chrome/views/scroll_view.cc b/chrome/views/scroll_view.cc new file mode 100644 index 0000000..a7b59b1 --- /dev/null +++ b/chrome/views/scroll_view.cc @@ -0,0 +1,534 @@ +// 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 "chrome/views/scroll_view.h" + +#include "base/logging.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/native_scroll_bar.h" +#include "chrome/views/root_view.h" + +namespace ChromeViews { + +const char* const ScrollView::kViewClassName = "chrome/views/ScrollView"; + +// Viewport contains the contents View of the ScrollView. +class Viewport : public View { + public: + Viewport() {} + virtual ~Viewport() {} + + virtual void ScrollRectToVisible(int x, int y, int width, int height) { + if (!GetChildViewCount() || !GetParent()) + return; + + View* contents = GetChildViewAt(0); + x -= contents->GetX(); + y -= contents->GetY(); + static_cast<ScrollView*>(GetParent())->ScrollContentsRegionToBeVisible( + x, y, width, height); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(Viewport); +}; + + +ScrollView::ScrollView() { + Init(new NativeScrollBar(true), new NativeScrollBar(false), NULL); +} + +ScrollView::ScrollView(ScrollBar* horizontal_scrollbar, + ScrollBar* vertical_scrollbar, + View* resize_corner) { + Init(horizontal_scrollbar, vertical_scrollbar, resize_corner); +} + +ScrollView::~ScrollView() { + // If scrollbars are currently not used, delete them + if (!horiz_sb_->GetParent()) { + delete horiz_sb_; + } + + if (!vert_sb_->GetParent()) { + delete vert_sb_; + } + + if (resize_corner_ && !resize_corner_->GetParent()) { + delete resize_corner_; + } +} + +void ScrollView::SetContents(View* a_view) { + if (contents_ && contents_ != a_view) { + viewport_->RemoveChildView(contents_); + delete contents_; + contents_ = NULL; + } + + if (a_view) { + contents_ = a_view; + viewport_->AddChildView(contents_); + } + + Layout(); +} + +View* ScrollView::GetContents() const { + return contents_; +} + +void ScrollView::Init(ScrollBar* horizontal_scrollbar, + ScrollBar* vertical_scrollbar, + View* resize_corner) { + DCHECK(horizontal_scrollbar && vertical_scrollbar); + + contents_ = NULL; + horiz_sb_ = horizontal_scrollbar; + vert_sb_ = vertical_scrollbar; + resize_corner_ = resize_corner; + + viewport_ = new Viewport(); + AddChildView(viewport_); + + // Don't add the scrollbars as children until we discover we need them + // (ShowOrHideScrollBar). + horiz_sb_->SetVisible(false); + horiz_sb_->SetController(this); + vert_sb_->SetVisible(false); + vert_sb_->SetController(this); + if (resize_corner_) + resize_corner_->SetVisible(false); +} + +// Make sure that a single scrollbar is created and visible as needed +void ScrollView::SetControlVisibility(View* control, bool should_show) { + if (!control) + return; + if (should_show) { + if (!control->IsVisible()) { + AddChildView(control); + control->SetVisible(true); + } + } else { + RemoveChildView(control); + control->SetVisible(false); + } +} + +void ScrollView::ComputeScrollBarsVisibility(const CSize& vp_size, + const CSize& content_size, + bool* horiz_is_shown, + bool* vert_is_shown) const { + // Try to fit both ways first, then try vertical bar only, then horizontal + // bar only, then defaults to both shown. + if (content_size.cx <= vp_size.cx && content_size.cy <= vp_size.cy) { + *horiz_is_shown = false; + *vert_is_shown = false; + } else if (content_size.cx <= vp_size.cx - GetScrollBarWidth()) { + *horiz_is_shown = false; + *vert_is_shown = true; + } else if (content_size.cy <= vp_size.cy - GetScrollBarHeight()) { + *horiz_is_shown = true; + *vert_is_shown = false; + } else { + *horiz_is_shown = true; + *vert_is_shown = true; + } +} + +void ScrollView::Layout() { + // Most views will want to auto-fit the available space. Most of them want to + // use the all available width (without overflowing) and only overflow in + // height. Examples are HistoryView, MostVisitedView, DownloadTabView, etc. + // Other views want to fit in both ways. An example is PrintView. To make both + // happy, assume a vertical scrollbar but no horizontal scrollbar. To + // override this default behavior, the inner view has to calculate the + // available space, used ComputeScrollBarsVisibility() to use the same + // calculation that is done here and sets its bound to fit within. + WTL::CRect viewport_bounds; + GetLocalBounds(&viewport_bounds, true); + // Realign it to 0 so it can be used as-is for SetBounds(). + viewport_bounds.MoveToXY(0, 0); + // viewport_size is the total client space available. + WTL::CSize viewport_size(viewport_bounds.right, + viewport_bounds.bottom); + if (viewport_size.cx == 0 || viewport_size.cy == 0) { + // There's nothing to layout. + return; + } + + // Assumes a vertical scrollbar since most the current views are designed for + // this. + int horiz_sb_height = GetScrollBarHeight(); + int vert_sb_width = GetScrollBarWidth(); + viewport_bounds.right -= vert_sb_width; + // Update the bounds right now so the inner views can fit in it. + viewport_->SetBounds(viewport_bounds); + + // Give contents_ a chance to update its bounds if it depends on the viewport. + if (contents_) { + contents_->Layout(); + } + + bool should_layout_contents = false; + bool horiz_sb_required = false; + bool vert_sb_required = false; + if (contents_) { + WTL::CSize content_size(contents_->GetWidth(), contents_->GetHeight()); + ComputeScrollBarsVisibility(viewport_size, + content_size, + &horiz_sb_required, + &vert_sb_required); + } + bool resize_corner_required = resize_corner_ && horiz_sb_required && + vert_sb_required; + // Take action. + SetControlVisibility(horiz_sb_, horiz_sb_required); + SetControlVisibility(vert_sb_, vert_sb_required); + SetControlVisibility(resize_corner_, resize_corner_required); + + // Non-default. + if (horiz_sb_required) { + viewport_bounds.bottom -= horiz_sb_height; + should_layout_contents = true; + } + // Default. + if (!vert_sb_required) { + viewport_bounds.right += vert_sb_width; + should_layout_contents = true; + } + + if (horiz_sb_required) { + horiz_sb_->SetBounds(0, + viewport_bounds.bottom, + viewport_bounds.right, + horiz_sb_height); + } + if (vert_sb_required) { + vert_sb_->SetBounds(viewport_bounds.right, + 0, + vert_sb_width, + viewport_bounds.bottom); + } + if (resize_corner_required) { + // Show the resize corner. + resize_corner_->SetBounds(viewport_bounds.right, + viewport_bounds.bottom, + vert_sb_width, + horiz_sb_height); + } + + // Update to the real client size with the visible scrollbars. + viewport_->SetBounds(viewport_bounds); + if (should_layout_contents && contents_) + contents_->Layout(); + + CheckScrollBounds(); + SchedulePaint(); + UpdateScrollBarPositions(); +} + +int ScrollView::CheckScrollBounds(int viewport_size, + int content_size, + int current_pos) { + int max = std::max(content_size - viewport_size, 0); + if (current_pos < 0) + current_pos = 0; + else if (current_pos > max) + current_pos = max; + return current_pos; +} + +void ScrollView::CheckScrollBounds() { + if (contents_) { + int x, y; + + x = CheckScrollBounds(viewport_->GetWidth(), + contents_->GetWidth(), + -contents_->GetX()); + y = CheckScrollBounds(viewport_->GetHeight(), + contents_->GetHeight(), + -contents_->GetY()); + + // This is no op if bounds are the same + contents_->SetBounds(-x, -y, contents_->GetWidth(), contents_->GetHeight()); + } +} + +void ScrollView::DidChangeBounds(const CRect& previous, const CRect& current) { + Layout(); +} + +gfx::Rect ScrollView::GetVisibleRect() const { + if (!contents_) + return gfx::Rect(); + + const int x = + (horiz_sb_ && horiz_sb_->IsVisible()) ? horiz_sb_->GetPosition() : 0; + const int y = + (vert_sb_ && vert_sb_->IsVisible()) ? vert_sb_->GetPosition() : 0; + return gfx::Rect(x, y, viewport_->GetWidth(), viewport_->GetHeight()); +} + +void ScrollView::ScrollContentsRegionToBeVisible(int x, + int y, + int width, + int height) { + if (!contents_ || ((!horiz_sb_ || !horiz_sb_->IsVisible()) && + (!vert_sb_ || !vert_sb_->IsVisible()))) { + return; + } + + const int contents_max_x = + std::max(viewport_->GetWidth(), contents_->GetWidth()); + const int contents_max_y = + std::max(viewport_->GetHeight(), contents_->GetHeight()); + x = std::max(0, std::min(contents_max_x, x)); + y = std::max(0, std::min(contents_max_y, y)); + const int max_x = std::min(contents_max_x, + x + std::min(width, viewport_->GetWidth())); + const int max_y = std::min(contents_max_y, + y + std::min(height, + viewport_->GetHeight())); + const gfx::Rect vis_rect = GetVisibleRect(); + if (vis_rect.Contains(gfx::Rect(x, y, max_x, max_y))) + return; + + const int new_x = + (vis_rect.x() < x) ? x : std::max(0, max_x - viewport_->GetWidth()); + const int new_y = + (vis_rect.y() < y) ? y : std::max(0, max_x - viewport_->GetHeight()); + + contents_->SetX(-new_x); + contents_->SetY(-new_y); + UpdateScrollBarPositions(); +} + +void ScrollView::UpdateScrollBarPositions() { + if (!contents_) { + return; + } + + if (horiz_sb_->IsVisible()) { + int vw = viewport_->GetWidth(); + int cw = contents_->GetWidth(); + int origin = contents_->GetX(); + horiz_sb_->Update(vw, cw, -origin); + } + if (vert_sb_->IsVisible()) { + int vh = viewport_->GetHeight(); + int ch = contents_->GetHeight(); + int origin = contents_->GetY(); + vert_sb_->Update(vh, ch, -origin); + } +} + +// TODO(ACW). We should really use ScrollWindowEx as needed +void ScrollView::ScrollToPosition(ScrollBar* source, int position) { + if (!contents_) + return; + + if (source == horiz_sb_ && horiz_sb_->IsVisible()) { + int vw = viewport_->GetWidth(); + int cw = contents_->GetWidth(); + int origin = contents_->GetX(); + if (-origin != position) { + int max_pos = std::max(0, cw - vw); + if (position < 0) + position = 0; + else if (position > max_pos) + position = max_pos; + contents_->SetX(-position); + CRect bounds; + contents_->GetLocalBounds(&bounds, true); + contents_->SchedulePaint(bounds, true); + } + } else if (source == vert_sb_ && vert_sb_->IsVisible()) { + int vh = viewport_->GetHeight(); + int ch = contents_->GetHeight(); + int origin = contents_->GetY(); + if (-origin != position) { + int max_pos = std::max(0, ch - vh); + if (position < 0) + position = 0; + else if (position > max_pos) + position = max_pos; + contents_->SetY(-position); + CRect bounds; + contents_->GetLocalBounds(&bounds, true); + contents_->SchedulePaint(bounds, true); + } + } +} + +int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page, + bool is_positive) { + bool is_horizontal = source->IsHorizontal(); + int amount = 0; + View* view = GetContents(); + if (view) { + if (is_page) + amount = view->GetPageScrollIncrement(this, is_horizontal, is_positive); + else + amount = view->GetLineScrollIncrement(this, is_horizontal, is_positive); + if (amount > 0) + return amount; + } + // No view, or the view didn't return a valid amount. + if (is_page) + return is_horizontal ? viewport_->GetWidth() : viewport_->GetHeight(); + return is_horizontal ? viewport_->GetWidth() / 5 : viewport_->GetHeight() / 5; +} + +void ScrollView::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (is_add) { + RootView* rv = GetRootView(); + if (rv) { + rv->SetDefaultKeyboardHandler(this); + rv->SetFocusOnMousePressed(true); + } + } +} + +bool ScrollView::OnKeyPressed(const KeyEvent& event) { + bool processed = false; + + // Give vertical scrollbar priority + if (vert_sb_->IsVisible()) { + processed = vert_sb_->OnKeyPressed(event); + } + + if (!processed && horiz_sb_->IsVisible()) { + processed = horiz_sb_->OnKeyPressed(event); + } + return processed; +} + +bool ScrollView::OnMouseWheel(const MouseWheelEvent& e) { + bool processed = false; + + // Give vertical scrollbar priority + if (vert_sb_->IsVisible()) { + processed = vert_sb_->OnMouseWheel(e); + } + + if (!processed && horiz_sb_->IsVisible()) { + processed = horiz_sb_->OnMouseWheel(e); + } + return processed; +} + +std::string ScrollView::GetClassName() const { + return kViewClassName; +} + +int ScrollView::GetScrollBarWidth() const { + return vert_sb_->GetLayoutSize(); +} + +int ScrollView::GetScrollBarHeight() const { + return horiz_sb_->GetLayoutSize(); +} + +// VariableRowHeightScrollHelper ---------------------------------------------- + +VariableRowHeightScrollHelper::VariableRowHeightScrollHelper( + Controller* controller) : controller_(controller) { +} + +VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() { +} + +int VariableRowHeightScrollHelper::GetPageScrollIncrement( + ScrollView* scroll_view, bool is_horizontal, bool is_positive) { + if (is_horizontal) + return 0; + // y coordinate is most likely negative. + int y = abs(scroll_view->GetContents()->GetY()); + int vis_height = scroll_view->GetContents()->GetParent()->GetHeight(); + if (is_positive) { + // Align the bottom most row to the top of the view. + int bottom = std::min(scroll_view->GetContents()->GetHeight() - 1, + y + vis_height); + RowInfo bottom_row_info = GetRowInfo(bottom); + // If 0, ScrollView will provide a default value. + return std::max(0, bottom_row_info.origin - y); + } else { + // Align the row on the previous page to to the top of the view. + int last_page_y = y - vis_height; + RowInfo last_page_info = GetRowInfo(std::max(0, last_page_y)); + if (last_page_y != last_page_info.origin) + return std::max(0, y - last_page_info.origin - last_page_info.height); + return std::max(0, y - last_page_info.origin); + } +} + +int VariableRowHeightScrollHelper::GetLineScrollIncrement( + ScrollView* scroll_view, bool is_horizontal, bool is_positive) { + if (is_horizontal) + return 0; + // y coordinate is most likely negative. + int y = abs(scroll_view->GetContents()->GetY()); + RowInfo row = GetRowInfo(y); + if (is_positive) { + return row.height - (y - row.origin); + } else if (y == row.origin) { + row = GetRowInfo(std::max(0, row.origin - 1)); + return y - row.origin; + } else { + return y - row.origin; + } +} + +VariableRowHeightScrollHelper::RowInfo + VariableRowHeightScrollHelper::GetRowInfo(int y) { + return controller_->GetRowInfo(y); +} + +// FixedRowHeightScrollHelper ----------------------------------------------- + +FixedRowHeightScrollHelper::FixedRowHeightScrollHelper(int top_margin, + int row_height) + : VariableRowHeightScrollHelper(NULL), + top_margin_(top_margin), + row_height_(row_height) { + DCHECK(row_height > 0); +} + +VariableRowHeightScrollHelper::RowInfo + FixedRowHeightScrollHelper::GetRowInfo(int y) { + if (y < top_margin_) + return RowInfo(0, top_margin_); + return RowInfo((y - top_margin_) / row_height_ * row_height_ + top_margin_, + row_height_); +} + +} diff --git a/chrome/views/scroll_view.h b/chrome/views/scroll_view.h new file mode 100644 index 0000000..e017868 --- /dev/null +++ b/chrome/views/scroll_view.h @@ -0,0 +1,233 @@ +// 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. + +#ifndef CHROME_VIEWS_SCROLL_VIEW_H__ +#define CHROME_VIEWS_SCROLL_VIEW_H__ + +#include "chrome/views/scroll_bar.h" + +namespace ChromeViews { + +///////////////////////////////////////////////////////////////////////////// +// +// ScrollView class +// +// A ScrollView is used to make any View scrollable. The view is added to +// a viewport which takes care of clipping. +// +// In this current implementation both horizontal and vertical scrollbars are +// added as needed. +// +// The scrollview supports keyboard UI and mousewheel. +// +///////////////////////////////////////////////////////////////////////////// + +class ScrollView : public View, + public ScrollBarController { + public: + static const char* const ScrollView::kViewClassName; + + ScrollView(); + // Initialize with specific views. resize_corner is optional. + ScrollView(ScrollBar* horizontal_scrollbar, + ScrollBar* vertical_scrollbar, + View* resize_corner); + virtual ~ScrollView(); + + // Set the contents. Any previous contents will be deleted. The contents + // is the view that needs to scroll. + void SetContents(View* a_view); + View* GetContents() const; + + // Overridden to layout the viewport and scrollbars. + virtual void Layout(); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Returns the visible region of the content View. + gfx::Rect GetVisibleRect() const; + + // Scrolls the minimum amount necessary to make the specified rectangle + // visible, in the coordinates of the contents view. The specified rectangle + // is constrained by the bounds of the contents view. This has no effect if + // the contents have not been set. + // + // Client code should use ScrollRectToVisible, which invokes this + // appropriately. + void ScrollContentsRegionToBeVisible(int x, int y, int width, int height); + + // ScrollBarController. + // NOTE: this is intended to be invoked by the ScrollBar, and NOT general + // client code. + // See also ScrollRectToVisible. + virtual void ScrollToPosition(ScrollBar* source, int position); + + // Returns the amount to scroll relative to the visible bounds. This invokes + // either GetPageScrollIncrement or GetLineScrollIncrement to determine the + // amount to scroll. If the view returns 0 (or a negative value) a default + // value is used. + virtual int GetScrollIncrement(ScrollBar* source, + bool is_page, + bool is_positive); + + // Overridden to setup keyboard ui when the view hierarchy changes + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Keyboard events + virtual bool OnKeyPressed(const KeyEvent& event); + virtual bool OnMouseWheel(const MouseWheelEvent& e); + + virtual std::string GetClassName() const; + + // Retrieves the vertical scrollbar width. + int GetScrollBarWidth() const; + + // Retrieves the horizontal scrollbar height. + int GetScrollBarHeight() const; + + // Computes the visibility of both scrollbars, taking in account the view port + // and content sizes. + void ComputeScrollBarsVisibility(const CSize& viewport_size, + const CSize& content_size, + bool* horiz_is_shown, + bool* vert_is_shown) const; + + ScrollBar* horizontal_scroll_bar() const { return horiz_sb_; } + + ScrollBar* vertical_scroll_bar() const { return vert_sb_; } + + private: + // Initialize the ScrollView. resize_corner is optional. + void Init(ScrollBar* horizontal_scrollbar, + ScrollBar* vertical_scrollbar, + View* resize_corner); + + // Shows or hides the scrollbar/resize_corner based on the value of + // |should_show|. + void SetControlVisibility(View* control, bool should_show); + + // Update the scrollbars positions given viewport and content sizes. + void UpdateScrollBarPositions(); + + // Make sure the content is not scrolled out of bounds + void CheckScrollBounds(); + + // Make sure the content is not scrolled out of bounds in one dimension + int CheckScrollBounds(int viewport_size, int content_size, int current_pos); + + // The clipping viewport. Content is added to that view. + View* viewport_; + + // The current contents + View* contents_; + + // Horizontal scrollbar. + ScrollBar* horiz_sb_; + + // Vertical scrollbar. + ScrollBar* vert_sb_; + + // Resize corner. + View* resize_corner_; + + DISALLOW_EVIL_CONSTRUCTORS(ScrollView); +}; + +// VariableRowHeightScrollHelper is intended for views that contain rows of +// varying height. To use a VariableRowHeightScrollHelper create one supplying +// a Controller and delegate GetPageScrollIncrement and GetLineScrollIncrement +// to the helper. VariableRowHeightScrollHelper calls back to the +// Controller to determine row boundaries. +class VariableRowHeightScrollHelper { + public: + // The origin and height of a row. + struct RowInfo { + RowInfo(int origin, int height) : origin(origin), height(height) {} + + // Origin of the row. + int origin; + + // Height of the row. + int height; + }; + + // Used to determine row boundaries. + class Controller { + public: + // Returns the origin and size of the row at the specified location. + virtual VariableRowHeightScrollHelper::RowInfo GetRowInfo(int y) = 0; + }; + + // Creates a new VariableRowHeightScrollHelper. Controller is + // NOT deleted by this VariableRowHeightScrollHelper. + explicit VariableRowHeightScrollHelper(Controller* controller); + virtual ~VariableRowHeightScrollHelper(); + + // Delegate the View methods of the same name to these. The scroll amount is + // determined by querying the Controller for the appropriate row to scroll + // to. + int GetPageScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive); + int GetLineScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive); + + protected: + // Returns the row information for the row at the specified location. This + // calls through to the method of the same name on the controller. + virtual RowInfo GetRowInfo(int y); + + private: + Controller* controller_; + + DISALLOW_EVIL_CONSTRUCTORS(VariableRowHeightScrollHelper); +}; + +// FixedRowHeightScrollHelper is intended for views that contain fixed height +// height rows. To use a FixedRowHeightScrollHelper delegate +// GetPageScrollIncrement and GetLineScrollIncrement to it. +class FixedRowHeightScrollHelper : public VariableRowHeightScrollHelper { + public: + // Creates a FixedRowHeightScrollHelper. top_margin gives the distance from + // the top of the view to the first row, and may be 0. row_height gives the + // height of each row. + FixedRowHeightScrollHelper(int top_margin, int row_height); + + protected: + // Calculates the bounds of the row from the top margin and row height. + virtual RowInfo GetRowInfo(int y); + + private: + int top_margin_; + int row_height_; + + DISALLOW_EVIL_CONSTRUCTORS(FixedRowHeightScrollHelper); +}; + +} + +#endif // CHROME_VIEWS_SCROLL_VIEW_H__ diff --git a/chrome/views/separator.cc b/chrome/views/separator.cc new file mode 100644 index 0000000..7273098 --- /dev/null +++ b/chrome/views/separator.cc @@ -0,0 +1,65 @@ +// 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 "chrome/views/separator.h" + +#include "base/logging.h" +#include "chrome/views/hwnd_view.h" + +namespace ChromeViews { + +static const int kSeparatorSize = 2; + +Separator::Separator() { + SetFocusable(false); +} + +Separator::~Separator() { +} + +HWND Separator::CreateNativeControl(HWND parent_container) { + SetFixedHeight(kSeparatorSize, CENTER); + + return ::CreateWindowEx(GetAdditionalExStyle(), L"STATIC", L"", + WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); +} + +LRESULT Separator::OnNotify(int w_param, LPNMHDR l_param) { + return 0; +} + +void Separator::GetPreferredSize(CSize* out) { + DCHECK(out); + out->cx = GetWidth(); + out->cy = fixed_height_; +} + +} diff --git a/chrome/views/separator.h b/chrome/views/separator.h new file mode 100644 index 0000000..f503013 --- /dev/null +++ b/chrome/views/separator.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef CHROME_VIEWS_SEPARATOR_H__ +#define CHROME_VIEWS_SEPARATOR_H__ + +#include "chrome/views/native_control.h" + +namespace ChromeViews { + +// The Separator class is a view that shows a line used to visually separate +// other views. The current implementation is only horizontal. + +class Separator : public NativeControl { + public: + Separator(); + virtual ~Separator(); + + // NativeControl overrides: + virtual HWND CreateNativeControl(HWND parent_container); + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + // View overrides: + virtual void GetPreferredSize(CSize* out); + + private: + + DISALLOW_EVIL_CONSTRUCTORS(Separator); +}; + +} +#endif // #define CHROME_VIEWS_SEPARATOR_H__
\ No newline at end of file diff --git a/chrome/views/tabbed_pane.cc b/chrome/views/tabbed_pane.cc new file mode 100644 index 0000000..8ca6155 --- /dev/null +++ b/chrome/views/tabbed_pane.cc @@ -0,0 +1,274 @@ +// 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 "chrome/views/tabbed_pane.h" + +#include <vssym32.h> + +#include "base/gfx/native_theme.h" +#include "base/logging.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/views/background.h" +#include "chrome/views/hwnd_view_container.h" +#include "skia/include/SkColor.h" +#include "base/gfx/skia_utils.h" + +namespace ChromeViews { + +// A background object that paints the tab panel background which may be +// rendered by the system visual styles system. +class TabBackground : public Background { + public: + explicit TabBackground() { + // TMT_FILLCOLORHINT returns a color value that supposedly + // approximates the texture drawn by PaintTabPanelBackground. + SkColor tab_page_color = + gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::TAB, TABP_BODY, 0, TMT_FILLCOLORHINT, + COLOR_3DFACE); + SetNativeControlColor(tab_page_color); + } + virtual ~TabBackground() {} + + virtual void Paint(ChromeCanvas* canvas, View* view) const { + HDC dc = canvas->beginPlatformPaint(); + RECT r = {0, 0, view->GetWidth(), view->GetHeight()}; + gfx::NativeTheme::instance()->PaintTabPanelBackground(dc, &r); + canvas->endPlatformPaint(); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(TabBackground); +}; + +TabbedPane::TabbedPane() : content_window_(NULL), listener_(NULL) { +} + +TabbedPane::~TabbedPane() { + // We own the tab views, let's delete them. + STLDeleteContainerPointers(tab_views_.begin(), tab_views_.end()); +} + +void TabbedPane::SetListener(Listener* listener) { + listener_ = listener; +} + +void TabbedPane::AddTab(const std::wstring& title, View* contents) { + AddTabAtIndex(static_cast<int>(tab_views_.size()), title, contents, true); +} + +void TabbedPane::AddTabAtIndex(int index, + const std::wstring& title, + View* contents, + bool select_if_first_tab) { + DCHECK(index <= static_cast<int>(tab_views_.size())); + contents->SetParentOwned(false); + tab_views_.insert(tab_views_.begin() + index, contents); + + TCITEM tcitem; + tcitem.mask = TCIF_TEXT; + + // If the locale is RTL, we set the TCIF_RTLREADING so that BiDi text is + // rendered properly on the tabs. + if (UILayoutIsRightToLeft()) { + tcitem.mask |= TCIF_RTLREADING; + } + + tcitem.pszText = const_cast<wchar_t*>(title.c_str()); + int result = TabCtrl_InsertItem(tab_control_, index, &tcitem); + DCHECK(result != -1); + + if (!contents->GetBackground()) { + contents->SetBackground(new TabBackground); + } + + if (tab_views_.size() == 1 && select_if_first_tab) { + // If this is the only tab displayed, make sure the contents is set. + content_window_->GetRootView()->AddChildView(contents); + } + + // The newly added tab may have made the contents window smaller. + ResizeContents(tab_control_); +} + +View* TabbedPane::RemoveTabAtIndex(int index) { + int tab_count = static_cast<int>(tab_views_.size()); + DCHECK(index >= 0 && index < tab_count); + + if (index < (tab_count - 1)) { + // Select the next tab. + SelectTabAt(index + 1); + } else { + // We are the last tab, select the previous one. + if (index > 0) { + SelectTabAt(index - 1); + } else { + // That was the last tab. Remove the contents. + content_window_->GetRootView()->RemoveAllChildViews(false); + } + } + TabCtrl_DeleteItem(tab_control_, index); + + // The removed tab may have made the contents window bigger. + ResizeContents(tab_control_); + + std::vector<View*>::iterator iter = tab_views_.begin() + index; + tab_views_.erase(iter); + + return *iter; +} + +void TabbedPane::SelectTabAt(int index) { + DCHECK(index < static_cast<int>(tab_views_.size())); + TabCtrl_SetCurSel(tab_control_, index); + DoSelectTabAt(index); +} + +int TabbedPane::GetTabCount() { + return TabCtrl_GetItemCount(tab_control_); +} + +HWND TabbedPane::CreateNativeControl(HWND parent_container) { + // Create the tab control. + // + // Note that we don't follow the common convention for NativeControl + // subclasses and we don't pass the value returned from + // NativeControl::GetAdditionalExStyle() as the dwExStyle parameter. Here is + // why: on RTL locales, if we pass NativeControl::GetAdditionalExStyle() when + // we basically tell Windows to create our HWND with the WS_EX_LAYOUTRTL. If + // we do that, then the HWND we create for |content_window_| below will + // inherit the WS_EX_LAYOUTRTL property and this will result in the contents + // being flipped, which is not what we want (because we handle mirroring in + // ChromeViews without the use of Windows' support for mirroring). Therefore, + // we initially create our HWND without the aforementioned property and we + // explicitly set this property our child is created. This way, on RTL + // locales, our tabs will be nicely rendered from right to left (by virtue of + // Windows doing the right thing with the TabbedPane HWND) and each tab + // contents will use an RTL layout correctly (by virtue of the mirroring + // infrastructure in ChromeViews doing the right thing with each View we put + // in the tab). + tab_control_ = ::CreateWindowEx(0, + WC_TABCONTROL, + L"", + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + + HFONT font = ResourceBundle::GetSharedInstance(). + GetFont(ResourceBundle::BaseFont).hfont(); + SendMessage(tab_control_, WM_SETFONT, reinterpret_cast<WPARAM>(font), FALSE); + + // Create the view container which is a child of the TabControl. + content_window_ = new HWNDViewContainer(); + content_window_->Init(tab_control_, gfx::Rect(), NULL, false); + + // Explicitly setting the WS_EX_LAYOUTRTL property for the HWND (see above + // for a thorough explanation regarding why we waited until |content_window_| + // if created before we set this property for the tabbed pane's HWND). + if (UILayoutIsRightToLeft()) { + l10n_util::HWNDSetRTLLayout(tab_control_); + } + + RootView* root_view = content_window_->GetRootView(); + root_view->SetLayoutManager(new FillLayout()); + DWORD sys_color = ::GetSysColor(COLOR_3DHILIGHT); + SkColor color = SkColorSetRGB(GetRValue(sys_color), GetGValue(sys_color), + GetBValue(sys_color)); + root_view->SetBackground(Background::CreateSolidBackground(color)); + + content_window_->SetFocusTraversableParentView(this); + ResizeContents(tab_control_); + return tab_control_; +} + +LRESULT TabbedPane::OnNotify(int w_param, LPNMHDR l_param) { + if (static_cast<LPNMHDR>(l_param)->code == TCN_SELCHANGE) { + int selected_tab = TabCtrl_GetCurSel(tab_control_); + DCHECK(selected_tab != -1); + DoSelectTabAt(selected_tab); + return TRUE; + } + return FALSE; +} + +void TabbedPane::DoSelectTabAt(int index) { + RootView* content_root = content_window_->GetRootView(); + + // Clear the focus if the focused view was on the tab. + FocusManager* focus_manager = GetFocusManager(); + DCHECK(focus_manager); + View* focused_view = focus_manager->GetFocusedView(); + if (focused_view && content_root->IsParentOf(focused_view)) + focus_manager->ClearFocus(); + + content_root->RemoveAllChildViews(false); + content_root->AddChildView(tab_views_[index]); + content_root->Layout(); + if (listener_) + listener_->TabSelectedAt(index); +} + +void TabbedPane::Layout() { + NativeControl::Layout(); + ResizeContents(GetNativeControlHWND()); +} + +RootView* TabbedPane::GetContentsRootView() { + return content_window_->GetRootView(); +} + +FocusTraversable* TabbedPane::GetFocusTraversable() { + return content_window_; +} + +void TabbedPane::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + NativeControl::ViewHierarchyChanged(is_add, parent, child); + + if (is_add && (child == this) && content_window_) { + // We have been added to a view hierarchy, update the FocusTraversable + // parent. + content_window_->SetFocusTraversableParent(GetRootView()); + } +} + +void TabbedPane::ResizeContents(HWND tab_control) { + DCHECK(tab_control); + CRect content_bounds; + if (!GetClientRect(tab_control, &content_bounds)) + return; + TabCtrl_AdjustRect(tab_control, FALSE, &content_bounds); + content_window_->MoveWindow(content_bounds.left, content_bounds.top, + content_bounds.Width(), content_bounds.Height(), + TRUE); +} + +} diff --git a/chrome/views/tabbed_pane.h b/chrome/views/tabbed_pane.h new file mode 100644 index 0000000..431a624 --- /dev/null +++ b/chrome/views/tabbed_pane.h @@ -0,0 +1,113 @@ +// 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. + +#ifndef CHROME_VIEWS_TABBED_PANE_H__ +#define CHROME_VIEWS_TABBED_PANE_H__ + +#include "chrome/views/native_control.h" + +namespace ChromeViews { + +// The TabbedPane class is a view that shows tabs. When the user clicks on a +// tab, the associated view is displayed. +// TODO (jcampan): implement GetPreferredSize(). +class HWNDViewContainer; + +class TabbedPane : public NativeControl { + public: + TabbedPane(); + virtual ~TabbedPane(); + + // An interface an object can implement to be notified about events within + // the TabbedPane. + class Listener { + public: + // Called when the tab at the specified |index| is selected by the user. + virtual void TabSelectedAt(int index) = 0; + }; + void SetListener(Listener* listener); + + // Adds a new tab at the end of this TabbedPane with the specified |title|. + // |contents| is the view displayed when the tab is selected and is owned by + // the TabbedPane. + void AddTab(const std::wstring& title, View* contents); + + // Adds a new tab at the specified |index| with the specified |title|. + // |contents| is the view displayed when the tab is selected and is owned by + // the TabbedPane. If |select_if_first_tab| is true and the tabbed pane is + // currently empty, the new tab is selected. If you pass in false for + // |select_if_first_tab| you need to explicitly invoke SelectTabAt, otherwise + // the tabbed pane will not have a valid selection. + void AddTabAtIndex(int index, + const std::wstring& title, + View* contents, + bool select_if_first_tab); + + // Removes the tab at the specified |index| and returns the associated content + // view. The caller becomes the owner of the returned view. + View* RemoveTabAtIndex(int index); + + // Selects the tab at the specified |index|. + void SelectTabAt(int index); + + // Returns the number of tabs. + int GetTabCount(); + + virtual HWND CreateNativeControl(HWND parent_container); + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + virtual void Layout(); + + virtual RootView* GetContentsRootView(); + virtual FocusTraversable* GetFocusTraversable(); + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + private: + // Changes the contents view to the view associated with the tab at |index|. + void DoSelectTabAt(int index); + + void ResizeContents(HWND tab_control); + + HWND tab_control_; + + // The views associated with the different tabs. + std::vector<View*> tab_views_; + + // The window displayed in the tab. + HWNDViewContainer* content_window_; + + // The listener we notify about tab selection changes. + Listener* listener_; + + DISALLOW_EVIL_CONSTRUCTORS(TabbedPane); +}; + +} + +#endif // #define CHROME_VIEWS_TABBED_PANE_H__
\ No newline at end of file diff --git a/chrome/views/table_view.cc b/chrome/views/table_view.cc new file mode 100644 index 0000000..5f985b7 --- /dev/null +++ b/chrome/views/table_view.cc @@ -0,0 +1,1025 @@ +// 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 <windowsx.h> + +#include "chrome/views/table_view.h" + +#include "base/string_util.h" +#include "base/win_util.h" +#include "base/gfx/skia_utils.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/favicon_size.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/view_container.h" +#include "SkBitmap.h" +#include "SkColorFilter.h" + +namespace ChromeViews { + +// Added to column width to prevent truncation. +const int kListViewTextPadding = 15; +// Additional column width necessary if column has icons. +const int kListViewIconWidthAndPadding = 18; + +SkBitmap TableModel::GetIcon(int row) { + return SkBitmap(); +} + +TableView::TableView(TableModel* model, + const std::vector<TableColumn>& columns, + TableTypes table_type, + bool single_selection, + bool resizable_columns, + bool autosize_columns) + : model_(model), + table_view_observer_(NULL), + visible_columns_(), + all_columns_(), + column_count_(static_cast<int>(columns.size())), + cache_data_(true), + table_type_(table_type), + single_selection_(single_selection), + ignore_listview_change_(false), + custom_colors_enabled_(false), + sized_columns_(false), + autosize_columns_(autosize_columns), + resizable_columns_(resizable_columns), + list_view_(NULL), + list_view_original_handler_(NULL), + header_original_handler_(NULL), + table_view_wrapper_(this), + custom_cell_font_(NULL), + content_offset_(0) { + DCHECK(model); + for (std::vector<TableColumn>::const_iterator i = columns.begin(); + i != columns.end(); ++i) { + AddColumn(*i); + visible_columns_.push_back(i->id); + } +} + +TableView::~TableView() { + if (list_view_) { + if (model_) + model_->SetObserver(NULL); + } + if (custom_cell_font_) + DeleteObject(custom_cell_font_); +} + +void TableView::SetModel(TableModel* model) { + model_ = model; + if (model_) + OnModelChanged(); +} + +void TableView::DidChangeBounds(const CRect& previous, + const CRect& current) { + if (!list_view_) + return; + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + Layout(); + if ((!sized_columns_ || autosize_columns_) && GetWidth() > 0) { + sized_columns_ = true; + ResetColumnSizes(); + } + UpdateContentOffset(); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + +int TableView::RowCount() { + if (!list_view_) + return 0; + return ListView_GetItemCount(list_view_); +} + +int TableView::SelectedRowCount() { + if (!list_view_) + return 0; + return ListView_GetSelectedCount(list_view_); +} + +void TableView::Select(int item) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < RowCount()); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + ignore_listview_change_ = true; + + // Unselect everything. + ListView_SetItemState(list_view_, -1, 0, LVIS_SELECTED); + + // Select the specified item. + ListView_SetItemState(list_view_, item, LVIS_SELECTED | LVIS_FOCUSED, + LVIS_SELECTED | LVIS_FOCUSED); + + // Make it visible. + ListView_EnsureVisible(list_view_, item, FALSE); + ignore_listview_change_ = false; + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); + if (table_view_observer_) + table_view_observer_->OnSelectionChanged(); +} + +void TableView::SetSelectedState(int item, bool state) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < RowCount()); + + ignore_listview_change_ = true; + + // Select the specified item. + ListView_SetItemState(list_view_, item, LVIS_SELECTED, LVIS_SELECTED); + + ignore_listview_change_ = false; +} + +void TableView::SetFocusOnItem(int item) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < RowCount()); + + ignore_listview_change_ = true; + + // Set the focus to the given item. + ListView_SetItemState(list_view_, item, LVIS_FOCUSED, LVIS_FOCUSED); + + ignore_listview_change_ = false; +} + +int TableView::FirstSelectedRow() { + if (!list_view_) + return -1; + + return ListView_GetNextItem(list_view_, -1, LVNI_ALL | LVIS_SELECTED); +} + + +bool TableView::IsItemSelected(int item) { + if (!list_view_) + return false; + + DCHECK(item >= 0 && item < RowCount()); + return (ListView_GetItemState(list_view_, item, LVIS_SELECTED) == + LVIS_SELECTED); +} + +bool TableView::ItemHasTheFocus(int item) { + if (!list_view_) + return false; + + DCHECK(item >= 0 && item < RowCount()); + return (ListView_GetItemState(list_view_, item, LVIS_FOCUSED) == + LVIS_FOCUSED); +} + +TableView::iterator TableView::SelectionBegin() { + return TableView::iterator(this, LastSelectedIndex()); +} + +TableView::iterator TableView::SelectionEnd() { + return TableView::iterator(this, -1); +} + +void TableView::OnItemsChanged(int start, int length) { + if (!list_view_) + return; + + if (length == -1) { + DCHECK(start >= 0); + if (cache_data_) { + length = model_->RowCount() - start; + } else { + length = RowCount() - start; + } + } + int row_count = RowCount(); + DCHECK(start >= 0 && length > 0 && start + length <= row_count); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + if (table_type_ == ICON_AND_TEXT) { + // The redraw event does not include the icon in the clip rect, preventing + // our icon from being repainted. So far the only way I could find around + // this is to change the image for the item. Even if the image does not + // exist, it causes the clip rect to include the icon's bounds so we can + // paint it in the post paint event. + LVITEM lv_item; + memset(&lv_item, 0, sizeof(LVITEM)); + lv_item.mask = LVIF_IMAGE; + for (int i = start; i < start + length; ++i) { + // Retrieve the current icon index. + lv_item.iItem = i; + BOOL r = ListView_GetItem(list_view_, &lv_item); + DCHECK(r); + // Set the current icon index to the other image. + lv_item.iImage = (lv_item.iImage + 1) % 2; + DCHECK((lv_item.iImage == 0) || (lv_item.iImage == 1)); + r = ListView_SetItem(list_view_, &lv_item); + DCHECK(r); + } + } + if (!cache_data_) { + ListView_RedrawItems(list_view_, start, start + length); + } else { + UpdateListViewCache(start, length, false); + } + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + + +void TableView::OnModelChanged() { + if (!list_view_) + return; + + int current_row_count = ListView_GetItemCount(list_view_); + if (current_row_count > 0) + OnItemsRemoved(0, current_row_count); + if (model_->RowCount()) + OnItemsAdded(0, model_->RowCount()); +} + +void TableView::OnItemsAdded(int start, int length) { + if (!list_view_) + return; + + DCHECK(start >= 0 && length > 0 && start <= RowCount()); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + if (!cache_data_) { + ListView_SetItemCount(list_view_, model_->RowCount()); + ListView_RedrawItems(list_view_, start, start + length); + } else { + UpdateListViewCache(start, length, true); + } + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + +void TableView::OnItemsRemoved(int start, int length) { + if (!list_view_) + return; + + DCHECK(start >= 0 && length > 0 && start + length <= RowCount()); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + bool had_selection = (SelectedRowCount() > 0); + if (!cache_data_) { + // TODO(sky): Make sure this triggers a repaint. + ListView_SetItemCount(list_view_, model_->RowCount()); + } else { + // Update the cache. + if (start == 0 && length == RowCount()) { + ListView_DeleteAllItems(list_view_); + } else { + for (int i = 0; i < length; ++i) { + ListView_DeleteItem(list_view_, start); + } + } + } + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); + // We don't seem to get notification in this case. + if (table_view_observer_ && had_selection && RowCount() == 0) + table_view_observer_->OnSelectionChanged(); +} + +void TableView::AddColumn(const TableColumn& col) { + DCHECK(all_columns_.count(col.id) == 0); + all_columns_[col.id] = col; +} + +void TableView::SetColumns(const std::vector<TableColumn>& columns) { + all_columns_.empty(); + for (std::vector<TableColumn>::const_iterator i = columns.begin(); + i != columns.end(); ++i) { + AddColumn(*i); + } +} + +void TableView::OnColumnsChanged() { + column_count_ = static_cast<int>(visible_columns_.size()); + ResetColumnSizes(); +} + +void TableView::SetColumnVisibility(int id, bool is_visible) { + bool changed = false; + for (std::vector<int>::iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) { + if (*i == id) { + if (is_visible) { + // It's already visible, bail out early. + return; + } else { + int index = static_cast<int>(i - visible_columns_.begin()); + // This could be called before the native list view has been created + // (in CreateNativeControl, called when the view is added to a + // container). In that case since the column is not in + // visible_columns_ it will not be added later on when it is created. + if (list_view_) + SendMessage(list_view_, LVM_DELETECOLUMN, index, 0); + visible_columns_.erase(i); + changed = true; + break; + } + } + } + if (is_visible) { + visible_columns_.push_back(id); + TableColumn& column = all_columns_[id]; + InsertColumn(column, column_count_); + if (column.min_visible_width == 0) { + // ListView_GetStringWidth must be padded or else truncation will occur. + column.min_visible_width = ListView_GetStringWidth(list_view_, + column.title.c_str()) + + kListViewTextPadding; + } + changed = true; + } + if (changed) + OnColumnsChanged(); +} + +void TableView::SetVisibleColumns(const std::vector<int>& columns) { + size_t old_count = visible_columns_.size(); + size_t new_count = columns.size(); + // remove the old columns + if (list_view_) { + for (std::vector<int>::reverse_iterator i = visible_columns_.rbegin(); + i != visible_columns_.rend(); ++i) { + int index = static_cast<int>(i - visible_columns_.rend()); + SendMessage(list_view_, LVM_DELETECOLUMN, index, 0); + } + } + visible_columns_ = columns; + // Insert the new columns. + if (list_view_) { + for (std::vector<int>::iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) { + int index = static_cast<int>(i - visible_columns_.end()); + InsertColumn(all_columns_[*i], index); + } + } + OnColumnsChanged(); +} + +bool TableView::IsColumnVisible(int id) const { + for (std::vector<int>::const_iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) + if (*i == id) { + return true; + } + return false; +} + +const TableColumn& TableView::GetColumnAtPosition(int pos) { + return all_columns_[visible_columns_[pos]]; +} + +bool TableView::HasColumn(int id) { + return all_columns_.count(id) > 0; +} + +void TableView::SetCustomColorsEnabled(bool custom_colors_enabled) { + custom_colors_enabled_ = custom_colors_enabled; +} + +bool TableView::GetCellColors(int row, + int column, + ItemColor* foreground, + ItemColor* background, + LOGFONT* logfont) { + return false; +} + +LRESULT CALLBACK TableView::TableHeaderWndProc(HWND window, UINT message, + WPARAM w_param, LPARAM l_param) { + TableView* table_view = reinterpret_cast<TableViewWrapper*>( + GetWindowLongPtr(window, GWLP_USERDATA))->table_view; + + switch (message) { + case WM_SETCURSOR: + if (!table_view->resizable_columns_) + // Prevents the cursor from changing to the resize cursor. + return TRUE; + break; + case WM_LBUTTONDBLCLK: + if (!table_view->resizable_columns_) + // Prevents the double-click on the column separator from auto-resizing + // the column. + return TRUE; + break; + default: + break; + } + DCHECK(table_view->header_original_handler_); + return CallWindowProc(table_view->header_original_handler_, + window, message, w_param, l_param); +} + +HWND TableView::CreateNativeControl(HWND parent_container) { + int style = WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS; + if (single_selection_) + style |= LVS_SINGLESEL; + if (!cache_data_) + style |= LVS_OWNERDATA; + // If there's only one column and the title string is empty, don't show a + // header. + if (all_columns_.size() == 1) { + std::map<int, ChromeViews::TableColumn>::const_iterator first = + all_columns_.begin(); + if (first->second.title.empty()) + style |= LVS_NOCOLUMNHEADER; + } + list_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(), + WC_LISTVIEW, + L"", + style, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + model_->SetObserver(this); + + // Make the selection extend across the row. + // Reduce overdraw/flicker artifacts by double buffering. + DWORD list_view_style = LVS_EX_FULLROWSELECT; + if (win_util::GetWinVersion() > win_util::WINVERSION_2000) { + list_view_style |= LVS_EX_DOUBLEBUFFER; + } + if (table_type_ == CHECK_BOX_AND_TEXT) + list_view_style |= LVS_EX_CHECKBOXES; + ListView_SetExtendedListViewStyleEx(list_view_, 0, list_view_style); + + // Add the columns. + for (std::vector<int>::iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) { + InsertColumn(all_columns_[*i], + static_cast<int>(i - visible_columns_.begin())); + } + + // Add the groups. + if (model_->HasGroups() && + win_util::GetWinVersion() > win_util::WINVERSION_2000) { + ListView_EnableGroupView(list_view_, true); + + TableModel::Groups groups = model_->GetGroups(); + LVGROUP group = { 0 }; + group.cbSize = sizeof(LVGROUP); + group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID; + group.uAlign = LVGA_HEADER_LEFT; + for (size_t i = 0; i < groups.size(); ++i) { + group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str()); + group.iGroupId = groups[i].id; + ListView_InsertGroup(list_view_, static_cast<int>(i), &group); + } + } + + // Set the # of rows. + if (cache_data_) { + UpdateListViewCache(0, model_->RowCount(), true); + } else if (table_type_ == CHECK_BOX_AND_TEXT) { + ListView_SetItemCount(list_view_, model_->RowCount()); + ListView_SetCallbackMask(list_view_, LVIS_STATEIMAGEMASK); + } + + // Load the default icon. + if (table_type_ == ICON_AND_TEXT) { + HIMAGELIST image_list = ImageList_Create(18, 18, ILC_COLOR, 1, 1); + // TODO(jcampan): include a default icon image. + // This icon will not be found. The list view will still layout with the + // right space for the icon so we can paint our own icon. + HBITMAP image = LoadBitmap(NULL, L"IDR_WHATEVER"); + ImageList_Add(image_list, image, NULL); + DeleteObject(image); + // We create 2 phony images because we are going to switch images at every + // refresh in order to force a refresh of the icon area (somehow the clip + // rect does not include the icon). + image = LoadBitmap(NULL, L"IDR_WHATEVER_AGAIN"); + ImageList_Add(image_list, image, NULL); + DeleteObject(image); + ListView_SetImageList(list_view_, image_list, LVSIL_SMALL); + } + + if (!resizable_columns_) { + // To disable the resizing of columns we'll filter the events happening on + // the header. We also need to intercept the HDM_LAYOUT to size the header + // for the Chrome headers. + HWND header = ListView_GetHeader(list_view_); + DCHECK(header); + SetWindowLongPtr(header, GWLP_USERDATA, + reinterpret_cast<LONG_PTR>(&table_view_wrapper_)); + header_original_handler_ = win_util::SetWindowProc(header, + &TableView::TableHeaderWndProc); + } + + // Bug 964884: detach the IME attached to this window. + // We should attach IMEs only when we need to input CJK strings. + ::ImmAssociateContextEx(list_view_, NULL, 0); + + UpdateContentOffset(); + + return list_view_; +} + +void TableView::InsertColumn(const TableColumn& tc, int index) { + if (!list_view_) + return; + + LVCOLUMN column = { 0 }; + column.mask = LVCF_TEXT|LVCF_FMT; + column.pszText = const_cast<LPWSTR>(tc.title.c_str()); + switch (tc.alignment) { + case TableColumn::LEFT: + column.fmt = LVCFMT_LEFT; + break; + case TableColumn::RIGHT: + column.fmt = LVCFMT_RIGHT; + break; + case TableColumn::CENTER: + column.fmt = LVCFMT_CENTER; + break; + default: + NOTREACHED(); + } + if (tc.width != -1) { + column.mask |= LVCF_WIDTH; + column.cx = tc.width; + } + column.mask |= LVCF_SUBITEM; + // Sub-items are 1s indexed. + column.iSubItem = index + 1; + SendMessage(list_view_, LVM_INSERTCOLUMN, index, + reinterpret_cast<LPARAM>(&column)); +} + +LRESULT TableView::OnNotify(int w_param, NMHDR* hdr) { + switch (hdr->code) { + case NM_CUSTOMDRAW: { + // Draw notification. dwDragState indicates the current stage of drawing. + return OnCustomDraw(reinterpret_cast<NMLVCUSTOMDRAW*>(hdr)); + } + case LVN_ITEMCHANGED: { + // Notification that the state of an item has changed. The state + // includes such things as whether the item is selected or checked. + NMLISTVIEW* state_change = reinterpret_cast<NMLISTVIEW*>(hdr); + if ((state_change->uChanged & LVIF_STATE) != 0) { + if ((state_change->uOldState & LVIS_SELECTED) != + (state_change->uNewState & LVIS_SELECTED)) { + // Selected state of the item changed. + bool is_selected = ((state_change->uNewState & LVIS_SELECTED) != 0); + OnSelectedStateChanged(state_change->iItem, is_selected); + } + if ((state_change->uOldState & LVIS_STATEIMAGEMASK) != + (state_change->uNewState & LVIS_STATEIMAGEMASK)) { + // Checked state of the item changed. + bool is_checked = + ((state_change->uNewState & LVIS_STATEIMAGEMASK) == + INDEXTOSTATEIMAGEMASK(2)); + OnCheckedStateChanged(state_change->iItem, is_checked); + } + } + break; + } + case HDN_BEGINTRACKW: + case HDN_BEGINTRACKA: + // Prevent clicks so columns cannot be resized. + if (!resizable_columns_) + return TRUE; + break; + case NM_DBLCLK: + OnDoubleClick(); + break; + + // If we see a key down message, we need to invoke the OnKeyDown handler + // in order to give our class (or any subclass) and opportunity to perform + // a key down triggered action, if such action is necessary. + case LVN_KEYDOWN: { + NMLVKEYDOWN* key_down_message = reinterpret_cast<NMLVKEYDOWN*>(hdr); + OnKeyDown(key_down_message->wVKey); + break; + } + + default: + break; + } + return 0; +} + +void TableView::OnDestroy() { + if (table_type_ == ICON_AND_TEXT) { + HIMAGELIST image_list = + ListView_GetImageList(GetNativeControlHWND(), LVSIL_SMALL); + DCHECK(image_list); + if (image_list) + ImageList_Destroy(image_list); + } +} + +LRESULT TableView::OnCustomDraw(NMLVCUSTOMDRAW* draw_info) { + switch (draw_info->nmcd.dwDrawStage) { + case CDDS_PREPAINT: { + return CDRF_NOTIFYITEMDRAW; + } + case CDDS_ITEMPREPAINT: { + // The list-view is about to paint an item, tell it we want to + // notified when it paints every subitem. + LRESULT r = CDRF_NOTIFYSUBITEMDRAW; + if (table_type_ == ICON_AND_TEXT) + r |= CDRF_NOTIFYPOSTPAINT; + return r; + } + case (CDDS_ITEMPREPAINT | CDDS_SUBITEM): { + // The list-view is painting a subitem. See if the colors should be + // changed from the default. + if (custom_colors_enabled_) { + // At this time, draw_info->clrText and draw_info->clrTextBk are not + // set. So we pass in an ItemColor to GetCellColors. If + // ItemColor.color_is_set is true, then we use the provided color. + ItemColor foreground = {0}; + ItemColor background = {0}; + + LOGFONT logfont; + GetObject(GetWindowFont(list_view_), sizeof(logfont), &logfont); + + if (GetCellColors(static_cast<int>(draw_info->nmcd.dwItemSpec), + draw_info->iSubItem, + &foreground, + &background, + &logfont)) { + // TODO(tc): Creating/deleting a font for every cell seems like a + // waste if the font hasn't changed. Maybe we should use a struct + // with a bool like we do with colors? + if (custom_cell_font_) + DeleteObject(custom_cell_font_); + custom_cell_font_ = CreateFontIndirect(&logfont); + SelectObject(draw_info->nmcd.hdc, custom_cell_font_); + draw_info->clrText = foreground.color_is_set + ? gfx::SkColorToCOLORREF(foreground.color) + : CLR_DEFAULT; + draw_info->clrTextBk = background.color_is_set + ? gfx::SkColorToCOLORREF(background.color) + : CLR_DEFAULT; + return CDRF_NEWFONT; + } + } + return CDRF_DODEFAULT; + } + case CDDS_ITEMPOSTPAINT: { + DCHECK((table_type_ == ICON_AND_TEXT) || (ImplementPostPaint())); + int n_item = static_cast<int>(draw_info->nmcd.dwItemSpec); + // We get notifications for empty items, just ignore them. + if (n_item >= model_->RowCount()) { + return CDRF_DODEFAULT; + } + LRESULT r = CDRF_DODEFAULT; + // First let's take care of painting the right icon. + if (table_type_ == ICON_AND_TEXT) { + SkBitmap image = model_->GetIcon(n_item); + if (!image.isNull()) { + // Get the rect that holds the icon. + CRect icon_rect, client_rect; + if (ListView_GetItemRect(list_view_, n_item, &icon_rect, + LVIR_ICON) && + GetClientRect(list_view_, &client_rect)) { + CRect intersection; + // Client rect includes the header but we need to make sure we don't + // paint into it. + client_rect.top += content_offset_; + // Make sure the region need to paint is visible. + if (intersection.IntersectRect(&icon_rect, &client_rect)) { + ChromeCanvas canvas(icon_rect.Width(), icon_rect.Height(), false); + + // It seems the state in nmcd.uItemState is not correct. + // We'll retrieve it explicitly. + int selected = ListView_GetItemState(list_view_, n_item, + LVIS_SELECTED); + int bg_color_index; + if (!IsEnabled()) + bg_color_index = COLOR_3DFACE; + else if (selected) + bg_color_index = HasFocus() ? COLOR_HIGHLIGHT : COLOR_3DFACE; + else + bg_color_index = COLOR_WINDOW; + // NOTE: This may be invoked without the ListView filling in the + // background (or rather windows paints background, then invokes + // this twice). As such, we always fill in the background. + canvas.drawColor( + gfx::COLORREFToSkColor(GetSysColor(bg_color_index)), + SkPorterDuff::kSrc_Mode); + // + 1 for padding (we declared the image as 18x18 in the list- + // view when they are 16x16 so we get an extra pixel of padding). + canvas.DrawBitmapInt(image, 0, 0, + image.width(), image.height(), + 1, 1, kFavIconSize, kFavIconSize, true); + + // Only paint the visible region of the icon. + RECT to_draw = { intersection.left - icon_rect.left, + intersection.top - icon_rect.top, + 0, 0 }; + to_draw.right = to_draw.left + + (intersection.right - intersection.left); + to_draw.bottom = to_draw.top + + (intersection.bottom - intersection.top); + canvas.getTopPlatformDevice().drawToHDC(draw_info->nmcd.hdc, + intersection.left, + intersection.top, + &to_draw); + r = CDRF_SKIPDEFAULT; + } + } + } + } + if (ImplementPostPaint()) { + CRect cell_rect; + if (ListView_GetItemRect(list_view_, n_item, &cell_rect, LVIR_BOUNDS)) { + PostPaint(n_item, 0, false, cell_rect, draw_info->nmcd.hdc); + r = CDRF_SKIPDEFAULT; + } + } + return r; + } + default: + return CDRF_DODEFAULT; + } +} + +void TableView::UpdateListViewCache(int start, int length, bool add) { + ignore_listview_change_ = true; + UpdateListViewCache0(start, length, add); + ignore_listview_change_ = false; +} + +void TableView::ResetColumnSizes() { + if (!list_view_) + return; + + // See comment in TableColumn for what this does. + CRect bounds; + GetLocalBounds(&bounds, false); // false so it doesn't include the border. + int width = bounds.Width(); + if (GetClientRect(GetNativeControlHWND(), &bounds) && + bounds.Width() > 0) { + // Prefer the bounds of the window over our bounds, which may be different. + width = bounds.Width(); + } + + float percent = 0; + int fixed_width = 0; + int autosize_width = 0; + + for (std::vector<int>::const_iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) { + TableColumn& col = all_columns_[*i]; + int col_index = static_cast<int>(i - visible_columns_.begin()); + if (col.width == -1) { + if (col.percent > 0) { + percent += col.percent; + } else { + autosize_width += col.min_visible_width; + } + } else { + fixed_width += ListView_GetColumnWidth(list_view_, col_index); + } + } + + // Now do a pass to set the actual sizes of auto-sized and + // percent-sized columns. + int available_width = width - fixed_width - autosize_width; + for (std::vector<int>::const_iterator i = visible_columns_.begin(); + i != visible_columns_.end(); ++i) { + TableColumn& col = all_columns_[*i]; + if (col.width == -1) { + int col_index = static_cast<int>(i - visible_columns_.begin()); + if (col.percent > 0) { + if (available_width > 0) { + int col_width = + static_cast<int>(available_width * (col.percent / percent)); + available_width -= col_width; + percent -= col.percent; + ListView_SetColumnWidth(list_view_, col_index, col_width); + } + } else { + int col_width = col.min_visible_width; + // If no "percent" columns, the last column acts as one, if auto-sized. + if (percent == 0.f && available_width > 0 && + col_index == column_count_ - 1) { + col_width += available_width; + } + ListView_SetColumnWidth(list_view_, col_index, col_width); + } + } + } +} + +void TableView::SetPreferredSize(const CSize& preferred_size) { + preferred_size_ = preferred_size; +} + +void TableView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = preferred_size_; +} + + +void TableView::UpdateListViewCache0(int start, int length, bool add) { + LVITEM item = {0}; + int start_column = 0; + int max_row = start + length; + const bool has_groups = + (win_util::GetWinVersion() > win_util::WINVERSION_2000 && + model_->HasGroups()); + if (has_groups) + item.mask = LVIF_GROUPID; + if (add) { + for (int i = start; i < max_row; ++i) { + item.iItem = i; + if (has_groups) + item.iGroupId = model_->GetGroupID(i); + ListView_InsertItem(list_view_, &item); + } + } + + memset(&item, 0, sizeof(LVITEM)); + + // NOTE: I don't quite get why the iSubItem in the following is not offset + // by 1. According to the docs it should be offset by one, but that doesn't + // work. + if (table_type_ == CHECK_BOX_AND_TEXT) { + start_column = 1; + item.iSubItem = 0; + item.mask = LVIF_TEXT | LVIF_STATE; + item.stateMask = LVIS_STATEIMAGEMASK; + for (int i = start; i < max_row; ++i) { + std::wstring text = model_->GetText(i, visible_columns_[0]); + item.iItem = i; + item.pszText = const_cast<LPWSTR>(text.c_str()); + item.state = INDEXTOSTATEIMAGEMASK(model_->IsChecked(i) ? 2 : 1) ; + ListView_SetItem(list_view_, &item); + } + } + if (start_column == column_count_) + return; + + item.stateMask = 0; + item.mask = LVIF_TEXT; + if (table_type_ == ICON_AND_TEXT) { + item.mask |= LVIF_IMAGE; + } + for (int j = start_column; j < column_count_; ++j) { + TableColumn& col = all_columns_[visible_columns_[j]]; + int max_text_width = ListView_GetStringWidth(list_view_, col.title.c_str()); + for (int i = start; i < max_row; ++i) { + item.iItem = i; + item.iSubItem = j; + std::wstring text = model_->GetText(i, visible_columns_[j]); + item.pszText = const_cast<LPWSTR>(text.c_str()); + item.iImage = 0; + ListView_SetItem(list_view_, &item); + + // Compute width in px, using current font. + int string_width = ListView_GetStringWidth(list_view_, item.pszText); + // The width of an icon belongs to the first column. + if (j == 0 && table_type_ == ICON_AND_TEXT) + string_width += kListViewIconWidthAndPadding; + max_text_width = std::max(string_width, max_text_width); + } + + // ListView_GetStringWidth must be padded or else truncation will occur + // (MSDN). 15px matches the Win32/LVSCW_AUTOSIZE_USEHEADER behavior. + max_text_width += kListViewTextPadding; + + // Protect against partial update. + if (max_text_width > col.min_visible_width || + (start == 0 && length == model_->RowCount())) { + col.min_visible_width = max_text_width; + } + } +} + +void TableView::OnDoubleClick() { + if (!ignore_listview_change_ && table_view_observer_) { + table_view_observer_->OnDoubleClick(); + } +} + +void TableView::OnSelectedStateChanged(int item, bool is_selected) { + if (!ignore_listview_change_ && table_view_observer_) { + table_view_observer_->OnSelectionChanged(); + } +} + +void TableView::OnCheckedStateChanged(int item, bool is_checked) { + if (!ignore_listview_change_) { + model_->SetChecked(item, is_checked); + } +} + +int TableView::NextSelectedIndex(int item) { + if (!list_view_) + return -1; + DCHECK(item >= 0); + if (item >= RowCount()) { + return LastSelectedIndex(); + } + // It seems if the list has only 1 element and it is selected that + // ListView_GetNextItem always returns 0. + if (RowCount() == 1) { + return -1; + } + return ListView_GetNextItem(list_view_, + item, LVNI_ALL | LVNI_SELECTED | LVNI_ABOVE); +} + +int TableView::LastSelectedIndex() { + if (!list_view_) + return -1; + int row_count = RowCount(); + int last_selected_row = -1; + if (row_count > 0) { + if (ListView_GetItemState(list_view_, row_count - 1, + LVIS_SELECTED) == LVIS_SELECTED) { + last_selected_row = row_count - 1; + } else { + last_selected_row = ListView_GetNextItem(list_view_, + row_count - 1, LVNI_ALL | LVNI_SELECTED | LVNI_ABOVE); + } + } + return last_selected_row; +} + +void TableView::UpdateContentOffset() { + content_offset_ = 0; + + if (!list_view_) + return; + + HWND header = ListView_GetHeader(list_view_); + if (!header) + return; + + POINT origin = {0, 0}; + MapWindowPoints(header, list_view_, &origin, 1); + + CRect header_bounds; + GetWindowRect(header, &header_bounds); + + content_offset_ = origin.y + header_bounds.Height(); +} + +// +// TableSelectionIterator +// +TableSelectionIterator::TableSelectionIterator(TableView* view, + int index) + : table_view_(view), index_(index) { +} + +TableSelectionIterator& TableSelectionIterator::operator=( + const TableSelectionIterator& other) { + index_ = other.index_; + return *this; +} + +bool TableSelectionIterator::operator==(const TableSelectionIterator& other) { + return (other.index_ == index_); +} + +bool TableSelectionIterator::operator!=(const TableSelectionIterator& other) { + return (other.index_ != index_); +} + +TableSelectionIterator& TableSelectionIterator::operator++() { + index_ = table_view_->NextSelectedIndex(index_); + return *this; +} + +int TableSelectionIterator::operator*() { + return index_; +} + +} // namespace diff --git a/chrome/views/table_view.h b/chrome/views/table_view.h new file mode 100644 index 0000000..1f3f5dc --- /dev/null +++ b/chrome/views/table_view.h @@ -0,0 +1,521 @@ +// 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. + +#ifndef CHROME_VIEWS_TABLE_VIEW_H__ +#define CHROME_VIEWS_TABLE_VIEW_H__ + +#include <windows.h> + +#include "base/logging.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/native_control.h" +#include "SkColor.h" + +class SkBitmap; + +// A TableView is a view that displays multiple rows with any number of columns. +// TableView is driven by a TableModel. The model returns the contents +// to display. TableModel also has an Observer which is used to notify +// TableView of changes to the model so that the display may be updated +// appropriately. +// TableView itself has an observer that is notified when the selection +// changes. +// TableView is a wrapper around the window type ListView in report mode. +namespace ChromeViews { + +class HWNDView; +class ListView; +class ListViewParent; +class TableView; +struct TableColumn; + +// The cells in the first column of a table can contain: +// - only text +// - a small icon (16x16) and some text +// - a check box and some text +typedef enum TableTypes { + TEXT_ONLY = 0, + ICON_AND_TEXT, + CHECK_BOX_AND_TEXT +}; + +// Any time the TableModel changes, it must notify its observer. +class TableModelObserver { + public: + // Invoked when the model has been completely changed. + virtual void OnModelChanged() = 0; + + // Invoked when a range of items has changed. + virtual void OnItemsChanged(int start, int length) = 0; + + // Invoked when new items are added. + virtual void OnItemsAdded(int start, int length) = 0; + + // Invoked when a range of items has been removed. + virtual void OnItemsRemoved(int start, int length) = 0; +}; + +// The model driving the TableView. +class TableModel { + public: + // See HasGroups, get GetGroupID for details as to how this is used. + struct Group { + // The title text for the group. + std::wstring title; + + // Unique id for the group. + int id; + }; + typedef std::vector<Group> Groups; + + // Number of rows in the model. + virtual int RowCount() = 0; + + // Returns the value at a particular location in text. + virtual std::wstring GetText(int row, int column_id) = 0; + + // Returns the small icon (16x16) that should be displayed in the first + // column before the text. This is only used when the TableView was created + // with the ICON_AND_TEXT table type. Returns an isNull() bitmap if there is + // no bitmap. + virtual SkBitmap GetIcon(int row); + + // Sets whether a particular row is checked. This is only invoked + // if the TableView was created with show_check_in_first_column true. + virtual void SetChecked(int row, bool is_checked) { + NOTREACHED(); + } + + // Returns whether a particular row is checked. This is only invoked + // if the TableView was created with show_check_in_first_column true. + virtual bool IsChecked(int row) { + return false; + } + + // Returns true if the TableView has groups. Groups provide a way to visually + // delineate the rows in a table view. When groups are enabled table view + // shows a visual separator for each group, followed by all the rows in + // the group. + // + // On win2k a visual separator is not rendered for the group headers. + virtual bool HasGroups() { return false; } + + // Returns the groups. + // This is only used if HasGroups returns true. + virtual Groups GetGroups() { + // If you override HasGroups to return true, you must override this as + // well. + NOTREACHED(); + return std::vector<Group>(); + } + + // Returns the group id of the specified row. + // This is only used if HasGroups returns true. + virtual int GetGroupID(int row) { + // If you override HasGroups to return true, you must override this as + // well. + NOTREACHED(); + return 0; + } + + // Sets the observer for the model. The TableView should NOT take ownership + // of the observer. + virtual void SetObserver(TableModelObserver* observer) = 0; +}; + +// TableColumn specifies the title, alignment and size of a particular column. +struct TableColumn { + typedef enum Alignment { + LEFT, RIGHT, CENTER + }; + + TableColumn() : id(0), title(), alignment(LEFT), width(-1), percent(), min_visible_width(0) {} + + TableColumn(int id, const std::wstring title, Alignment alignment, int width) + : id(id), + title(title), + alignment(alignment), + width(width), + percent(0), + min_visible_width(0) { + } + TableColumn(int id, const std::wstring title, Alignment alignment, int width, + float percent) + : id(id), + title(title), + alignment(alignment), + width(width), + percent(percent), + min_visible_width(0) { + } + // It's common (but not required) to use the title's IDS_* tag as the column + // id. In this case, the provided conveniences look up the title string on + // bahalf of the caller. + TableColumn(int id, Alignment alignment, int width) + : id(id), + alignment(alignment), + width(width), + percent(0), + min_visible_width(0) { + title = l10n_util::GetString(id); + } + TableColumn(int id, Alignment alignment, int width, float percent) + : id(id), + alignment(alignment), + width(width), + percent(percent), + min_visible_width(0) { + title = l10n_util::GetString(id); + } + + // A unique identifier for the column. + int id; + + // The title for the column. + std::wstring title; + + // Alignment for the content. + Alignment alignment; + + // The size of a column may be specified in two ways: + // 1. A fixed width. Set the width field to a positive number and the + // column will be given that width, in pixels. + // 2. As a percentage of the available width. If width is -1, and percent is + // > 0, the column is given a width of + // available_width * percent / total_percent. + // 3. If the width == -1 and percent == 0, the column is autosized based on + // the width of the column header text. + // + // Sizing is done in four passes. Fixed width columns are given + // their width, percentages are applied, autosized columns are autosized, + // and finally percentages are applied again taking into account the widths + // of autosized columns. + int width; + float percent; + + // The minimum width required for all items in this column (including the header) + // to be visible. + int min_visible_width; +}; + +// Returned from SelectionBegin/SelectionEnd +class TableSelectionIterator { + public: + TableSelectionIterator(TableView* view, int index); + TableSelectionIterator& operator=(const TableSelectionIterator& other); + bool operator==(const TableSelectionIterator& other); + bool operator!=(const TableSelectionIterator& other); + TableSelectionIterator& operator++(); + int operator*(); + + private: + TableView* table_view_; + int index_; +}; + +// TableViewObserver is notified about the TableView selection. +class TableViewObserver { + public: + // Invoked when the selection changes. + virtual void OnSelectionChanged() = 0; + + // Optional method invoked when the user double clicks on the table. + virtual void OnDoubleClick() {} +}; + +class TableView : public NativeControl, + public TableModelObserver { + public: + typedef TableSelectionIterator iterator; + + // A helper struct for GetCellColors. Set |color_is_set| to true if color is + // set. See OnCustomDraw for more details on why we need this. + struct ItemColor { + bool color_is_set; + SkColor color; + }; + + // Creates a new table using the model and columns specified. + // The table type applies to the content of the first column (text, icon and + // text, checkbox and text). + // When autosize_columns is true, columns always fill the available width. If + // false, columns are not resized when the table is resized. An extra empty + // column at the right fills the remaining space. + // When resizable_columns is true, users can resize columns by dragging the + // separator on the column header. NOTE: Right now this is always true. The + // code to set it false is still in place to be a base for future, better + // resizing behavior (see http://b/issue?id=874646 ), but no one uses or + // tests the case where this flag is false. + // Note that setting both resizable_columns and autosize_columns to false is + // probably not a good idea, as there is no way for the user to increase a + // column's size in that case. + TableView(TableModel* model, const std::vector<TableColumn>& columns, + TableTypes table_type, bool single_selection, + bool resizable_columns, bool autosize_columns); + virtual ~TableView(); + + // Assigns a new model to the table view, detaching the old one if present. + // If |model| is NULL, the table view cannot be used after this call. This + // should be called in the containing view's destructor to avoid destruction + // issues when the model needs to be deleted before the table. + void SetModel(TableModel* model); + + void DidChangeBounds(const CRect& previous, const CRect& current); + + // Returns the number of rows in the TableView. + int RowCount(); + + // Returns the number of selected rows. + int SelectedRowCount(); + + // Selects the specified item, making sure it's visible. + void Select(int item); + + // Sets the selected state of an item (without sending any selection + // notifications). Note that this routine does NOT set the focus to the + // item at the given index. + void SetSelectedState(int item, bool state); + + // Sets the focus to the item at the given index. + void SetFocusOnItem(int item); + + // Returns the first selected row. + int FirstSelectedRow(); + + // Returns true if the item at the specified index is selected. + bool IsItemSelected(int item); + + // Returns true if the item at the specified index has the focus. + bool ItemHasTheFocus(int item); + + // Returns an iterator over the selection. The iterator proceeds from the + // last index to the first. Do NOT use the iterator after you've mutated + // the model. + iterator SelectionBegin(); + iterator SelectionEnd(); + + // TableModelObserver methods. + virtual void OnModelChanged(); + virtual void OnItemsChanged(int start, int length); + virtual void OnItemsAdded(int start, int length); + virtual void OnItemsRemoved(int start, int length); + + void SetObserver(TableViewObserver* observer) { + table_view_observer_ = observer; + } + + // Replaces the set of known columns without changing the current visible + // columns. + void SetColumns(const std::vector<TableColumn>& columns); + void AddColumn(const TableColumn& col); + bool HasColumn(int id); + + // Sets which columns (by id) are displayed. All transient size and position + // information is lost. + void SetVisibleColumns(const std::vector<int>& columns); + void SetColumnVisibility(int id, bool is_visible); + bool IsColumnVisible(int id) const; + + // Resets the size of the columns based on the sizes passed to the + // constructor. Your normally needn't invoked this, it's done for you the + // first time the TableView is given a valid size. + void ResetColumnSizes(); + + // Sometimes we may want to size the TableView to a specific width and + // height. + void SetPreferredSize(const CSize& preferred_size); + virtual void GetPreferredSize(CSize* out); + + protected: + // Subclasses that want to customize the colors of a particular row/column, + // must invoke this passing in true. The default value is false, such that + // GetCellColors is never invoked. + void SetCustomColorsEnabled(bool custom_colors_enabled); + + // Notification from the ListView that the selected state of an item has + // changed. + virtual void OnSelectedStateChanged(int item, bool is_selected); + + // Notification from the ListView that the used double clicked the table. + virtual void OnDoubleClick(); + + // Invoked to customize the colors or font at a particular cell. If you + // change the colors or font, return true. This is only invoked if + // SetCustomColorsEnabled(true) has been invoked. + virtual bool GetCellColors(int row, + int column, + ItemColor* foreground, + ItemColor* background, + LOGFONT* logfont); + + // Subclasses that want to perform some custom painting (on top of the regular + // list view painting) should return true here and implement the PostPaint + // method. + virtual bool ImplementPostPaint() { return false; } + // Subclasses can implement in this method extra-painting for cells. + virtual void PostPaint(int row, int column, bool selected, + const CRect& bounds, HDC device_context) { } + + // Subclasses can implement this method if they need to be notified of a key + // press event. + virtual void OnKeyDown(unsigned short virtual_keycode) {} + + virtual HWND CreateNativeControl(HWND parent_container); + + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + // Overriden to destroy the image list. + virtual void OnDestroy(); + + private: + // We need this wrapper to pass the table view to the windows proc handler + // when subclassing the list view and list view header, as the reinterpret + // cast from GetWindowLongPtr would break the pointer if it is pointing to a + // subclass (in the OO sense of TableView). + struct TableViewWrapper { + TableViewWrapper(TableView* view) : table_view(view) { } + TableView* table_view; + }; + + friend class ListViewParent; + friend class TableSelectionIterator; + + LRESULT OnCustomDraw(NMLVCUSTOMDRAW* draw_info); + + // Adds a new column. + void InsertColumn(const TableColumn& tc, int index); + + // Update headers and internal state after columns have changed + void OnColumnsChanged(); + + // Updates the ListView with values from the model. See UpdateListViewCache0 + // for a complete description. + // This turns off redrawing, and invokes UpdateListViewCache0 to do the + // actual updating. + void UpdateListViewCache(int start, int length, bool add); + + // Updates ListView with values from the model. + // If add is true, this adds length items starting at index start. + // If add is not true, the items are not added, the but the values in the + // range start - [start + length] are updated from the model. + void UpdateListViewCache0(int start, int length, bool add); + + // Notification from the ListView that the checked state of the item has + // changed. + void OnCheckedStateChanged(int item, bool is_checked); + + // Returns the index of the selected item after |item|, or -1 if |item| is + // the last selected item. + int NextSelectedIndex(int item); + + // Returns the last selected index in the table view, or -1 if the table + // is empty, or nothing is selected. + int LastSelectedIndex(); + + // The TableColumn visible at position pos. + const TableColumn& GetColumnAtPosition(int pos); + + // Window procedure of the header class. We subclass the header of the table + // to disable resizing of columns. + static LRESULT CALLBACK TableHeaderWndProc(HWND window, UINT message, + WPARAM w_param, LPARAM l_param); + + // Updates content_offset_ from the position of the header. + void UpdateContentOffset(); + + TableModel* model_; + TableTypes table_type_; + TableViewObserver* table_view_observer_; + + // An ordered list of id's into all_columns_ representing current visible + // columns. + std::vector<int> visible_columns_; + + // Mapping of an int id to a TableColumn representing all possible columns. + std::map<int,ChromeViews::TableColumn> all_columns_; + + // Cached value of columns_.size() + int column_count_; + + // Whether or not the data should be cached in the TableView. + // This is currently always true. + bool cache_data_; + + // Selection mode. + bool single_selection_; + + // If true, any events that would normally be propagated to the observer + // are ignored. For example, if this is true and the selection changes in + // the listview, the observer is not notified. + bool ignore_listview_change_; + + // Reflects the value passed to SetCustomColorsEnabled. + bool custom_colors_enabled_; + + // Whether or not the columns have been sized in the ListView. This is + // set to true the first time Layout() is invoked and we have a valid size. + bool sized_columns_; + + // Whether or not columns should automatically be resized to fill the + // the available width when the list view is resized. + bool autosize_columns_; + + // Whether or not the user can resize columns. + bool resizable_columns_; + + // NOTE: While this has the name View in it, it's not a view. Rather it's + // a wrapper around the List-View window. + HWND list_view_; + + // The list view original proc handler. It is required when subclassing the + // list view. + WNDPROC list_view_original_handler_; + + // The list view's header original proc handler. It is required when + // subclassing. + WNDPROC header_original_handler_; + + // A wrapper around 'this' used when "subclassing" the list view and header. + TableViewWrapper table_view_wrapper_; + + // A custom font we use when overriding the font type for a specific cell. + HFONT custom_cell_font_; + + // The preferred size of the table view. + CSize preferred_size_; + + // The offset from the top of the client area to the start of the content. + int content_offset_; + + DISALLOW_EVIL_CONSTRUCTORS(TableView); +}; + +} + +#endif // CHROME_VIEWS_TABLE_VIEW_H__ diff --git a/chrome/views/text_button.cc b/chrome/views/text_button.cc new file mode 100644 index 0000000..19a7eab --- /dev/null +++ b/chrome/views/text_button.cc @@ -0,0 +1,329 @@ +// 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 "chrome/views/text_button.h" + +#include <atlbase.h> +#include <atlapp.h> + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/button.h" +#include "chrome/views/event.h" +#include "chrome/views/view_menu_delegate.h" +#include "chrome/views/view_container.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +// Padding between the icon and text. +static const int kIconTextPadding = 5; + +// Preferred padding between text and edge +static const int kPreferredPaddingHorizontal = 6; +static const int kPreferredPaddingVertical = 5; + +static const SkColor kEnabledColor = SkColorSetRGB(6, 45, 117); +static const SkColor kHighlightColor = SkColorSetARGB(200, 255, 255, 255); +static const SkColor kDisabledColor = SkColorSetRGB(161, 161, 146); + +// How long the hover fade animation should last. +static const int kHoverAnimationDurationMs = 170; + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBorder - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +TextButtonBorder::TextButtonBorder() { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + hot_set_.top_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_LEFT_H); + hot_set_.top = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_H); + hot_set_.top_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_RIGHT_H); + hot_set_.left = rb.GetBitmapNamed(IDR_TEXTBUTTON_LEFT_H); + hot_set_.center = rb.GetBitmapNamed(IDR_TEXTBUTTON_CENTER_H); + hot_set_.right = rb.GetBitmapNamed(IDR_TEXTBUTTON_RIGHT_H); + hot_set_.bottom_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_LEFT_H); + hot_set_.bottom = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_H); + hot_set_.bottom_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_RIGHT_H); + + pushed_set_.top_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_LEFT_P); + pushed_set_.top = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_P); + pushed_set_.top_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_RIGHT_P); + pushed_set_.left = rb.GetBitmapNamed(IDR_TEXTBUTTON_LEFT_P); + pushed_set_.center = rb.GetBitmapNamed(IDR_TEXTBUTTON_CENTER_P); + pushed_set_.right = rb.GetBitmapNamed(IDR_TEXTBUTTON_RIGHT_P); + pushed_set_.bottom_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_LEFT_P); + pushed_set_.bottom = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_P); + pushed_set_.bottom_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_RIGHT_P); +} + +TextButtonBorder::~TextButtonBorder() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBackground - painting +// +//////////////////////////////////////////////////////////////////////////////// + +void TextButtonBorder::Paint(const View& view, ChromeCanvas* canvas) const { + const TextButton* mb = static_cast<const TextButton*>(&view); + int state = mb->GetState(); + + // TextButton takes care of deciding when to call Paint. + const MBBImageSet* set = &hot_set_; + if (state == TextButton::BS_PUSHED) + set = &pushed_set_; + + if (set) { + CRect bounds; + view.GetBounds(&bounds); + + // Draw the top left image + canvas->DrawBitmapInt(*set->top_left, 0, 0); + + // Tile the top image + canvas->TileImageInt(*set->top, + set->top_left->width(), 0, + bounds.Width() - set->top_right->width() - set->top_left->width(), + set->top->height()); + + // Draw the top right image + canvas->DrawBitmapInt(*set->top_right, + bounds.Width() - set->top_right->width(), 0); + + // Tile the left image + canvas->TileImageInt(*set->left, + 0, set->top_left->height(), + set->top_left->width(), + bounds.Height() - set->top->height() - set->bottom_left->height()); + + // Tile the center image + canvas->TileImageInt(*set->center, + set->left->width(), set->top->height(), + bounds.Width() - set->right->width() - set->left->width(), + bounds.Height() - set->bottom->height() - set->top->height()); + + // Tile the right image + canvas->TileImageInt(*set->right, + bounds.Width() - set->right->width(), set->top_right->height(), + bounds.Width(), bounds.Height() - set->bottom_right->height() - set->top_right->height()); + + // Draw the bottom left image + canvas->DrawBitmapInt(*set->bottom_left, 0, bounds.Height() - set->bottom_left->height()); + + // Tile the bottom image + canvas->TileImageInt(*set->bottom, + set->bottom_left->width(), bounds.Height() - set->bottom->height(), + bounds.Width() - set->bottom_right->width() - set->bottom_left->width(), + set->bottom->height()); + + // Draw the bottom right image + canvas->DrawBitmapInt(*set->bottom_right, + bounds.Width() - set->bottom_right->width(), + bounds.Height() - set->bottom_right->height()); + } else { + // Do nothing + } +} + +void TextButtonBorder::GetInsets(gfx::Insets* insets) const { + insets->Set(kPreferredPaddingVertical, kPreferredPaddingHorizontal, + kPreferredPaddingVertical, kPreferredPaddingHorizontal); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButton - Implementation +// +//////////////////////////////////////////////////////////////////////////////// + +TextButton::TextButton(const std::wstring& text) + : max_text_size_(CSize(0, 0)), + font_(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont)), + color_(kEnabledColor), + BaseButton(), + max_width_(0), + alignment_(ALIGN_LEFT) { + SetText(text); + SetBorder(new TextButtonBorder); + SetAnimationDuration(kHoverAnimationDurationMs); +} + +TextButton::~TextButton() { +} + +void TextButton::GetPreferredSize(CSize *result) { + gfx::Insets insets = GetInsets(); + + // Use the max size to set the button boundaries. + result->cx = max_text_size_.cx + icon_.width() + insets.width(); + result->cy = std::max(static_cast<int>(max_text_size_.cy), icon_.height()) + + insets.height(); + + if (icon_.width() > 0 && !text_.empty()) + result->cx += kIconTextPadding; + + if (max_width_ > 0) + result->cx = std::min(max_width_, static_cast<int>(result->cx)); +} + +void TextButton::GetMinimumSize(CSize *result) { + result->cx = max_text_size_.cx; + result->cy = max_text_size_.cy; +} + +bool TextButton::OnMousePressed(const ChromeViews::MouseEvent& e) { + return true; +} + +void TextButton::SetText(const std::wstring& text) { + text_ = text; + // Update our new current and max text size + text_size_.cx = font_.GetStringWidth(text_); + text_size_.cy = font_.height(); + max_text_size_.cx = std::max(max_text_size_.cx, text_size_.cx); + max_text_size_.cy = std::max(max_text_size_.cy, text_size_.cy); +} + +void TextButton::SetIcon(const SkBitmap& icon) { + icon_ = icon; +} + +void TextButton::ClearMaxTextSize() { + max_text_size_ = text_size_; +} + +void TextButton::Paint(ChromeCanvas* canvas) { + Paint(canvas, false); +} + +void TextButton::Paint(ChromeCanvas* canvas, bool for_drag) { + if (!for_drag) { + PaintBackground(canvas); + + if (hover_animation_->IsAnimating()) { + // Draw the hover bitmap into an offscreen buffer, then blend it + // back into the current canvas. + canvas->saveLayerAlpha(NULL, + static_cast<int>(hover_animation_->GetCurrentValue() * 255), + SkCanvas::kARGB_NoClipLayer_SaveFlag); + canvas->drawARGB(0, 255, 255, 255, SkPorterDuff::kClear_Mode); + PaintBorder(canvas); + canvas->restore(); + } else if (state_ == BS_HOT || state_ == BS_PUSHED) { + PaintBorder(canvas); + } + + PaintFocusBorder(canvas); + } + + gfx::Insets insets = GetInsets(); + int available_width = GetWidth() - insets.width(); + int available_height = GetHeight() - insets.height(); + // Use the actual text (not max) size to properly center the text. + int content_width = text_size_.cx; + if (icon_.width() > 0) { + content_width += icon_.width(); + if (!text_.empty()) + content_width += kIconTextPadding; + } + // Place the icon along the left edge. + int icon_x; + if (alignment_ == ALIGN_LEFT) { + icon_x = insets.left(); + } else if (alignment_ == ALIGN_RIGHT) { + icon_x = available_width - content_width; + } else { + icon_x = + std::max(0, (available_width - content_width) / 2) + insets.left(); + } + int text_x = icon_x; + if (icon_.width() > 0) + text_x += icon_.width() + kIconTextPadding; + const int text_width = std::min(static_cast<int>(text_size_.cx), + GetWidth() - insets.right() - text_x); + int text_y = (available_height - text_size_.cy) / 2 + insets.top(); + + if (text_width > 0) { + // Because the text button can (at times) draw multiple elements on the + // canvas, we can not mirror the button by simply flipping the canvas as + // doing this will mirror the text itself. Flipping the canvas will also + // make the icons look wrong because icons are almost always represented as + // direction insentisive bitmaps and such bitmaps should never be flipped + // horizontally. + // + // Due to the above, we must perform the flipping manually for RTL UIs. + gfx::Rect text_bounds(text_x, text_y, text_width, text_size_.cy); + text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); + + // Draw bevel highlight + canvas->DrawStringInt(text_, + font_, + kHighlightColor, + text_bounds.x() + 1, + text_bounds.y() + 1, + text_bounds.width(), + text_bounds.height()); + + canvas->DrawStringInt(text_, + font_, + color_, + text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height()); + } + + if (icon_.width() > 0) { + int icon_y = (available_height - icon_.height()) / 2 + insets.top(); + + // Mirroring the icon position if necessary. + gfx::Rect icon_bounds(icon_x, icon_y, icon_.width(), icon_.height()); + icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); + canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); + } +} + +void TextButton::SetEnabled(bool enabled) { + if (enabled == IsEnabled()) + return; + BaseButton::SetEnabled(enabled); + color_ = enabled ? kEnabledColor : kDisabledColor; + SchedulePaint(); +} + +} // namespace ChromeViews diff --git a/chrome/views/text_button.h b/chrome/views/text_button.h new file mode 100644 index 0000000..174b50f --- /dev/null +++ b/chrome/views/text_button.h @@ -0,0 +1,165 @@ +// 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. + +#ifndef CHROME_VIEWS_TEXT_BUTTON_H__ +#define CHROME_VIEWS_TEXT_BUTTON_H__ + +#include <windows.h> + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/border.h" +#include "chrome/views/base_button.h" +#include "skia/include/SkBitmap.h" + +namespace ChromeViews { + +class MouseEvent; + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBorder +// +// A Border subclass that paints a TextButton's background layer - +// basically the button frame in the hot/pushed states. +// +//////////////////////////////////////////////////////////////////////////////// +class TextButtonBorder : public Border { + public: + TextButtonBorder(); + virtual ~TextButtonBorder(); + + // Render the background for the provided view + virtual void Paint(const View& view, ChromeCanvas* canvas) const; + + // Returns the insets for the border. + virtual void GetInsets(gfx::Insets* insets) const; + + private: + DISALLOW_EVIL_CONSTRUCTORS(TextButtonBorder); + + // Images + struct MBBImageSet { + SkBitmap* top_left; + SkBitmap* top; + SkBitmap* top_right; + SkBitmap* left; + SkBitmap* center; + SkBitmap* right; + SkBitmap* bottom_left; + SkBitmap* bottom; + SkBitmap* bottom_right; + }; + MBBImageSet hot_set_; + MBBImageSet pushed_set_; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButton +// +// A button which displays text and/or and icon that can be changed in +// response to actions. TextButton reserves space for the largest string +// passed to SetText. To reset the cached max size invoke ClearMaxTextSize. +// +//////////////////////////////////////////////////////////////////////////////// +class TextButton : public BaseButton { +public: + TextButton(const std::wstring& text); + virtual ~TextButton(); + + typedef enum TextAlignment { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + + void GetPreferredSize(CSize* result); + void GetMinimumSize(CSize* result); + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + + // Call SetText once per string in your set of possible values at + // button creation time, so that it can contain the largest of them + // and avoid resizing the button when the text changes. + virtual void SetText(const std::wstring& text); + + void TextButton::SetTextAlignment(TextAlignment alignment) { + alignment_ = alignment; + } + + const std::wstring& GetText() { return text_; } + + // Sets the icon. + void SetIcon(const SkBitmap& icon); + + const SkBitmap& GetIcon() { return icon_; } + + // TextButton remembers the maximum display size of the text passed to + // SetText. This method resets the cached maximum display size to the + // current size. + void ClearMaxTextSize(); + + virtual void Paint(ChromeCanvas* canvas); + virtual void Paint(ChromeCanvas* canvas, bool for_drag); + + // Sets the enabled state. Setting the enabled state resets the color. + virtual void SetEnabled(bool enabled); + + // Sets the max width. The preferred width of the button will never be larger + // then the specified value. A value <= 0 indicates the preferred width + // is not constrained in anyway. + void set_max_width(int max_width) { max_width_ = max_width; } + + private: + std::wstring text_; + CSize text_size_; + + // Track the size of the largest text string seen so far, so that + // changing text_ will not resize the button boundary. + CSize max_text_size_; + + TextAlignment alignment_; + + ChromeFont font_; + + // Text color. + SkColor color_; + + SkBitmap icon_; + + // See setter for details. + int max_width_; + + DISALLOW_EVIL_CONSTRUCTORS(TextButton); +}; + + +} // namespace + +#endif // CHROME_VIEWS_TEXT_BUTTON_H__ diff --git a/chrome/views/text_field.cc b/chrome/views/text_field.cc new file mode 100644 index 0000000..9d2d3e8 --- /dev/null +++ b/chrome/views/text_field.cc @@ -0,0 +1,1028 @@ +// 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 "chrome/views/text_field.h" + +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlctrls.h> +#include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl +#include <vsstyle.h> + +#include "base/gfx/native_theme.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/clipboard_service.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/win_util.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/menu.h" +#include "chrome/views/view_container.h" + +#include "generated_resources.h" + +using gfx::NativeTheme; + +namespace ChromeViews { + +static const int kDefaultEditStyle = WS_CHILD | WS_VISIBLE; + +class TextField::Edit + : public CWindowImpl<TextField::Edit, CRichEditCtrl, + CWinTraits<kDefaultEditStyle> >, + public CRichEditCommands<TextField::Edit>, + public Menu::Delegate { + public: + DECLARE_WND_CLASS(L"ChromeViewsTextFieldEdit"); + + Edit(TextField* parent, bool draw_border); + ~Edit(); + + std::wstring GetText() const; + void SetText(const std::wstring& text); + + std::wstring GetSelectedText() const; + + // Selects all the text in the edit. Use this in place of SetSelAll() to + // avoid selecting the "phantom newline" at the end of the edit. + void SelectAll(); + + // Clears the selection within the edit field and sets the caret to the end. + void ClearSelection(); + + // Removes the border. + void RemoveBorder(); + + void SetEnabled(bool enabled); + + // CWindowImpl + BEGIN_MSG_MAP(Edit) + MSG_WM_CHAR(OnChar) + MSG_WM_CONTEXTMENU(OnContextMenu) + MSG_WM_COPY(OnCopy) + MSG_WM_CUT(OnCut) + MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_LBUTTONUP(OnLButtonUp) + MSG_WM_MBUTTONDOWN(OnNonLButtonDown) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_MOUSELEAVE(OnMouseLeave) + MSG_WM_NCCALCSIZE(OnNCCalcSize) + MSG_WM_NCPAINT(OnNCPaint) + MSG_WM_RBUTTONDOWN(OnNonLButtonDown) + MSG_WM_PASTE(OnPaste) + MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down + MSG_WM_SYSKEYDOWN(OnKeyDown) + END_MSG_MAP() + + // Menu::Delegate + virtual bool IsCommandEnabled(int id) const; + virtual void ExecuteCommand(int id); + + private: + // This object freezes repainting of the edit until the object is destroyed. + // Some methods of the CRichEditCtrl draw synchronously to the screen. If we + // don't freeze, the user will see a rapid series of calls to these as + // flickers. + // + // Freezing the control while it is already frozen is permitted; the control + // will unfreeze once both freezes are released (the freezes stack). + class ScopedFreeze { + public: + ScopedFreeze(Edit* edit, ITextDocument* text_object_model); + ~ScopedFreeze(); + + private: + Edit* const edit_; + ITextDocument* const text_object_model_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedFreeze); + }; + + // message handlers + void OnChar(TCHAR key, UINT repeat_count, UINT flags); + void OnContextMenu(HWND window, const CPoint& point); + void OnCopy(); + void OnCut(); + LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); + void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); + void OnLButtonDblClk(UINT keys, const CPoint& point); + void OnLButtonDown(UINT keys, const CPoint& point); + void OnLButtonUp(UINT keys, const CPoint& point); + void OnMouseLeave(); + void OnMouseMove(UINT keys, const CPoint& point); + int OnNCCalcSize(BOOL w_param, LPARAM l_param); + void OnNCPaint(HRGN region); + void OnNonLButtonDown(UINT keys, const CPoint& point); + void OnPaste(); + void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags); + + // Helper function for OnChar() and OnKeyDown() that handles keystrokes that + // could change the text in the edit. + void HandleKeystroke(UINT message, TCHAR key, UINT repeat_count, UINT flags); + + // Every piece of code that can change the edit should call these functions + // before and after the change. These functions determine if anything + // meaningful changed, and do any necessary updating and notification. + void OnBeforePossibleChange(); + void OnAfterPossibleChange(); + + // Given an X coordinate in client coordinates, returns that coordinate + // clipped to be within the horizontal bounds of the visible text. + // + // This is used in our mouse handlers to work around quirky behaviors of the + // underlying CRichEditCtrl like not supporting triple-click when the user + // doesn't click on the text itself. + // + // |is_triple_click| should be true iff this is the third click of a triple + // click. Sadly, we need to clip slightly differently in this case. + LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const; + + // Sets whether the mouse is in the edit. As necessary this redraws the + // edit. + void SetContainsMouse(bool contains_mouse); + + // Getter for the text_object_model_, used by the ScopedFreeze class. Note + // that the pointer returned here is only valid as long as the Edit is still + // alive. + ITextDocument* GetTextObjectModel() const; + + // We need to know if the user triple-clicks, so track double click points + // and times so we can see if subsequent clicks are actually triple clicks. + bool tracking_double_click_; + CPoint double_click_point_; + DWORD double_click_time_; + + // Used to discard unnecessary WM_MOUSEMOVE events after the first such + // unnecessary event. See detailed comments in OnMouseMove(). + bool can_discard_mousemove_; + + // The text of this control before a possible change. + std::wstring text_before_change_; + + // If true, the mouse is over the edit. + bool contains_mouse_; + + static bool did_load_library_; + + TextField* parent_; + + // The context menu for the edit. + scoped_ptr<Menu> context_menu_; + + // Border insets. + gfx::Insets content_insets_; + + // Whether the border is drawn. + bool draw_border_; + + // This interface is useful for accessing the CRichEditCtrl at a low level. + mutable CComQIPtr<ITextDocument> text_object_model_; + + DISALLOW_EVIL_CONSTRUCTORS(Edit); +}; + +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +TextField::Edit::ScopedFreeze::ScopedFreeze(TextField::Edit* edit, + ITextDocument* text_object_model) + : edit_(edit), + text_object_model_(text_object_model) { + // Freeze the screen. + if (text_object_model_) { + long count; + text_object_model_->Freeze(&count); + } +} + +TextField::Edit::ScopedFreeze::~ScopedFreeze() { + // Unfreeze the screen. + if (text_object_model_) { + long count; + text_object_model_->Unfreeze(&count); + if (count == 0) { + // We need to UpdateWindow() here instead of InvalidateRect() because, as + // far as I can tell, the edit likes to synchronously erase its background + // when unfreezing, thus requiring us to synchronously redraw if we don't + // want flicker. + edit_->UpdateWindow(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// TextField::Edit + +bool TextField::Edit::did_load_library_ = false; + +TextField::Edit::Edit(TextField* parent, bool draw_border) + : parent_(parent), + tracking_double_click_(false), + double_click_time_(0), + can_discard_mousemove_(false), + contains_mouse_(false), + draw_border_(draw_border) { + if (!did_load_library_) + did_load_library_ = !!LoadLibrary(L"riched20.dll"); + + DWORD style = kDefaultEditStyle; + if (parent->GetStyle() & TextField::STYLE_PASSWORD) + style |= ES_PASSWORD; + + if (parent->read_only_) + style |= ES_READONLY; + + if (parent->GetStyle() & TextField::STYLE_MULTILINE) + style |= ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL; + else + style |= ES_AUTOHSCROLL; + // Make sure we apply RTL related extended window styles if necessary. + DWORD ex_style = l10n_util::GetExtendedStyles(); + + RECT r = {0, 0, parent_->GetWidth(), parent_->GetHeight()}; + Create(parent_->GetViewContainer()->GetHWND(), r, NULL, style, ex_style); + + // Set up the text_object_model_. + CComPtr<IRichEditOle> ole_interface; + ole_interface.Attach(GetOleInterface()); + text_object_model_ = ole_interface; + + context_menu_.reset(new Menu(this, Menu::TOPLEFT, m_hWnd)); + context_menu_->AppendMenuItemWithLabel(IDS_UNDO, + l10n_util::GetString(IDS_UNDO)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel(IDS_CUT, + l10n_util::GetString(IDS_CUT)); + context_menu_->AppendMenuItemWithLabel(IDS_COPY, + l10n_util::GetString(IDS_COPY)); + context_menu_->AppendMenuItemWithLabel(IDS_PASTE, + l10n_util::GetString(IDS_PASTE)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel(IDS_SELECTALL, + l10n_util::GetString(IDS_SELECTALL)); +} + +TextField::Edit::~Edit() { +} + +std::wstring TextField::Edit::GetText() const { + int len = GetTextLength() + 1; + std::wstring str; + GetWindowText(WriteInto(&str, len), len); + return str; +} + +void TextField::Edit::SetText(const std::wstring& text) { + // Adjusting the string direction before setting the text in order to make + // sure both RTL and LTR strings are displayed properly. + std::wstring localized_text; + if (l10n_util::AdjustStringForLocaleDirection(text, &localized_text)) + SetWindowText(localized_text.c_str()); + else + SetWindowText(text.c_str()); +} + +std::wstring TextField::Edit::GetSelectedText() const { + // Figure out the length of the selection. + long start; + long end; + GetSel(start, end); + + // Grab the selected text. + std::wstring str; + GetSelText(WriteInto(&str, end - start + 1)); + + return str; +} + +void TextField::Edit::SelectAll() { + // Using (0, -1) here is equivalent to calling SetSelAll(); both will select + // the "phantom newline" that we're trying to avoid. + SetSel(0, GetTextLength()); +} + +void TextField::Edit::ClearSelection() { + SetSel(GetTextLength(), GetTextLength()); +} + +void TextField::Edit::RemoveBorder() { + if (!draw_border_) + return; + + draw_border_ = false; + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE | + SWP_NOOWNERZORDER | SWP_NOSIZE); +} + +void TextField::Edit::SetEnabled(bool enabled) { + SendMessage(parent_->GetNativeComponent(), WM_ENABLE, + static_cast<WPARAM>(enabled), 0); +} + +bool TextField::Edit::IsCommandEnabled(int id) const { + switch (id) { + case IDS_UNDO: return !parent_->IsReadOnly() && !!CanUndo(); + case IDS_CUT: return !parent_->IsReadOnly() && !!CanCut(); + case IDS_COPY: return !!CanCopy(); + case IDS_PASTE: return !parent_->IsReadOnly() && !!CanPaste(); + case IDS_SELECTALL: return !!CanSelectAll(); + default: NOTREACHED(); + return false; + } +} + +void TextField::Edit::ExecuteCommand(int id) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + switch (id) { + case IDS_UNDO: Undo(); break; + case IDS_CUT: Cut(); break; + case IDS_COPY: Copy(); break; + case IDS_PASTE: Paste(); break; + case IDS_SELECTALL: SelectAll(); break; + default: NOTREACHED(); break; + } + OnAfterPossibleChange(); +} + +void TextField::Edit::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { + HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); +} + +void TextField::Edit::OnContextMenu(HWND window, const CPoint& point) { + CPoint p(point); + if (point.x == -1 || point.y == -1) { + GetCaretPos(&p); + MapWindowPoints(HWND_DESKTOP, &p, 1); + } + context_menu_->RunMenuAt(p.x, p.y); +} + +void TextField::Edit::OnCopy() { + const std::wstring text(GetSelectedText()); + + if (!text.empty()) { + ClipboardService* clipboard = g_browser_process->clipboard_service(); + + clipboard->Clear(); + clipboard->WriteText(text); + } +} + +void TextField::Edit::OnCut() { + if (parent_->IsReadOnly()) + return; + + OnCopy(); + + // This replace selection will have no effect (even on the undo stack) if the + // current selection is empty. + ReplaceSel(L"", true); +} + +LRESULT TextField::Edit::OnImeComposition(UINT message, + WPARAM wparam, + LPARAM lparam) { + OnBeforePossibleChange(); + LRESULT result = DefWindowProc(message, wparam, lparam); + OnAfterPossibleChange(); + return result; +} + +void TextField::Edit::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { + // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than + // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places + // in this function even with a WM_SYSKEYDOWN handler. + + switch (key) { + case VK_RETURN: + // If we are multi-line, we want to let returns through so they start a + // new line. + if (parent_->IsMultiLine()) + break; + else + return; + // Hijacking Editing Commands + // + // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that + // they go through our clipboard routines. This allows us to be smarter + // about how we interact with the clipboard and avoid bugs in the + // CRichEditCtrl. If we didn't hijack here, the edit control would handle + // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. + // + // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and + // Ctrl-Shift-x are not treated as cut even though the underlying + // CRichTextEdit would treat them as such. + // Copy: Ctrl-v is treated as copy. Shift-Ctrl-v is not. + // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and + // Ctrl-Shift-v are not. + // + // This behavior matches most, but not all Windows programs, and largely + // conforms to what users expect. + + case VK_DELETE: + case 'X': + if ((flags & KF_ALTDOWN) || + (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) + break; + if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Cut(); + OnAfterPossibleChange(); + } + return; + + case 'C': + if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) + break; + if (GetKeyState(VK_SHIFT) >= 0) + Copy(); + return; + + case VK_INSERT: + case 'V': + if ((flags & KF_ALTDOWN) || + (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) + break; + if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Paste(); + OnAfterPossibleChange(); + } + return; + + case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. + return; + } + + // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many + // different keys (backspace, ctrl-v, ...), so we call this in both cases. + HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags); +} + +void TextField::Edit::OnLButtonDblClk(UINT keys, const CPoint& point) { + // Save the double click info for later triple-click detection. + tracking_double_click_ = true; + double_click_point_ = point; + double_click_time_ = GetCurrentMessage()->time; + + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDBLCLK, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + OnAfterPossibleChange(); +} + +void TextField::Edit::OnLButtonDown(UINT keys, const CPoint& point) { + // Check for triple click, then reset tracker. Should be safe to subtract + // double_click_time_ from the current message's time even if the timer has + // wrapped in between. + const bool is_triple_click = tracking_double_click_ && + win_util::IsDoubleClick(double_click_point_, point, + GetCurrentMessage()->time - double_click_time_); + tracking_double_click_ = false; + + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDOWN, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), + point.y)); + OnAfterPossibleChange(); +} + +void TextField::Edit::OnLButtonUp(UINT keys, const CPoint& point) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONUP, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + OnAfterPossibleChange(); +} + +void TextField::Edit::OnMouseLeave() { + SetContainsMouse(false); +} + +void TextField::Edit::OnMouseMove(UINT keys, const CPoint& point) { + SetContainsMouse(true); + // Clamp the selection to the visible text so the user can't drag to select + // the "phantom newline". In theory we could achieve this by clipping the X + // coordinate, but in practice the edit seems to behave nondeterministically + // with similar sequences of clipped input coordinates fed to it. Maybe it's + // reading the mouse cursor position directly? + // + // This solution has a minor visual flaw, however: if there's a visible + // cursor at the edge of the text (only true when there's no selection), + // dragging the mouse around outside that edge repaints the cursor on every + // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we + // special-case this exact case and discard the WM_MOUSEMOVE messages instead + // of passing them along. + // + // But even this solution has a flaw! (Argh.) In the case where the user + // has a selection that starts at the edge of the edit, and proceeds to the + // middle of the edit, and the user is dragging back past the start edge to + // remove the selection, there's a redraw problem where the change between + // having the last few bits of text still selected and having nothing + // selected can be slow to repaint (which feels noticeably strange). This + // occurs if you only let the edit receive a single WM_MOUSEMOVE past the + // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its + // previous state, then updating its internal variables to the new state but + // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after + // the selection has supposedly been shrunk to nothing; this makes the edit + // redraw the selection quickly so it feels smooth. + CHARRANGE selection; + GetSel(selection); + const bool possibly_can_discard_mousemove = + (selection.cpMin == selection.cpMax) && + (((selection.cpMin == 0) && + (ClipXCoordToVisibleText(point.x, false) > point.x)) || + ((selection.cpMin == GetTextLength()) && + (ClipXCoordToVisibleText(point.x, false) < point.x))); + if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { + can_discard_mousemove_ = possibly_can_discard_mousemove; + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + // Force the Y coordinate to the center of the clip rect. The edit + // behaves strangely when the cursor is dragged vertically: if the cursor + // is in the middle of the text, drags inside the clip rect do nothing, + // and drags outside the clip rect act as if the cursor jumped to the + // left edge of the text. When the cursor is at the right edge, drags of + // just a few pixels vertically end up selecting the "phantom newline"... + // sometimes. + RECT r; + GetRect(&r); + DefWindowProc(WM_MOUSEMOVE, keys, + MAKELPARAM(point.x, (r.bottom - r.top) / 2)); + OnAfterPossibleChange(); + } +} + +int TextField::Edit::OnNCCalcSize(BOOL w_param, LPARAM l_param) { + content_insets_.Set(0, 0, 0, 0); + parent_->CalculateInsets(&content_insets_); + if (w_param) { + NCCALCSIZE_PARAMS* nc_params = + reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); + nc_params->rgrc[0].left += content_insets_.left(); + nc_params->rgrc[0].right -= content_insets_.right(); + nc_params->rgrc[0].top += content_insets_.top(); + nc_params->rgrc[0].bottom -= content_insets_.bottom(); + } else { + RECT* rect = reinterpret_cast<RECT*>(l_param); + rect->left += content_insets_.left(); + rect->right -= content_insets_.right(); + rect->top += content_insets_.top(); + rect->bottom -= content_insets_.bottom(); + } + return 0; +} + +void TextField::Edit::OnNCPaint(HRGN region) { + if (!draw_border_) + return; + + HDC hdc = GetWindowDC(); + + CRect window_rect; + GetWindowRect(&window_rect); + // Convert to be relative to 0x0. + window_rect.MoveToXY(0, 0); + + ExcludeClipRect(hdc, + window_rect.left + content_insets_.left(), + window_rect.top + content_insets_.top(), + window_rect.right - content_insets_.right(), + window_rect.bottom - content_insets_.bottom()); + + FillRect(hdc, &window_rect, (HBRUSH) (COLOR_WINDOW+1)); + + int part; + int state; + + if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { + part = EP_EDITTEXT; + + if (!parent_->IsEnabled()) + state = ETS_DISABLED; + else if (parent_->IsReadOnly()) + state = ETS_READONLY; + else if (!contains_mouse_) + state = ETS_NORMAL; + else + state = ETS_HOT; + } else { + part = EP_EDITBORDER_HVSCROLL; + + if (!parent_->IsEnabled()) + state = EPSHV_DISABLED; + else if (GetFocus() == m_hWnd) + state = EPSHV_FOCUSED; + else if (contains_mouse_) + state = EPSHV_HOT; + else + state = EPSHV_NORMAL; + // Vista doesn't appear to have a unique state for readonly. + } + + int classic_state = + (!parent_->IsEnabled() || parent_->IsReadOnly()) ? DFCS_INACTIVE : 0; + + NativeTheme::instance()->PaintTextField(hdc, part, state, classic_state, + &window_rect, NULL, false, true); + + // NOTE: I tried checking the transparent property of the theme and invoking + // drawParentBackground, but it didn't seem to make a difference. + + ReleaseDC(hdc); +} + +void TextField::Edit::OnNonLButtonDown(UINT keys, const CPoint& point) { + // Interestingly, the edit doesn't seem to cancel triple clicking when the + // x-buttons (which usually means "thumb buttons") are pressed, so we only + // call this for M and R down. + tracking_double_click_ = false; + SetMsgHandled(false); +} + +void TextField::Edit::OnPaste() { + if (parent_->IsReadOnly()) + return; + + ClipboardService* clipboard = g_browser_process->clipboard_service(); + + if (!clipboard->IsFormatAvailable(CF_UNICODETEXT)) + return; + + std::wstring clipboard_str; + clipboard->ReadText(&clipboard_str); + if (!clipboard_str.empty()) { + const std::wstring collapsed(CollapseWhitespace(clipboard_str, false)); + ReplaceSel(collapsed.c_str(), true); + } +} + +void TextField::Edit::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) { + // Nearly all alt-<xxx> combos result in beeping rather than doing something + // useful, so we discard most. Exceptions: + // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead + // of WM_SYSCHAR, so it doesn't need to be handled here. + // * alt-space gets translated by the default WM_SYSCHAR handler to a + // WM_SYSCOMMAND to open the application context menu, so we need to allow + // it through. + if (ch == VK_SPACE) + SetMsgHandled(false); +} + +void TextField::Edit::HandleKeystroke(UINT message, + TCHAR key, + UINT repeat_count, + UINT flags) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); + OnAfterPossibleChange(); + + TextField::Controller* controller = parent_->GetController(); + if (controller) + controller->HandleKeystroke(parent_, message, key, repeat_count, flags); +} + +void TextField::Edit::OnBeforePossibleChange() { + // Record our state. + text_before_change_ = GetText(); +} + +void TextField::Edit::OnAfterPossibleChange() { + // Prevent the user from selecting the "phantom newline" at the end of the + // edit. If they try, we just silently move the end of the selection back to + // the end of the real text. + CHARRANGE new_sel; + GetSel(new_sel); + const int length = GetTextLength(); + if (new_sel.cpMax > length) { + new_sel.cpMax = length; + if (new_sel.cpMin > length) + new_sel.cpMin = length; + SetSel(new_sel); + } + + const std::wstring new_text(GetText()); + if (new_text != text_before_change_) { + parent_->SyncText(); + if (parent_->GetController()) + parent_->GetController()->ContentsChanged(parent_, new_text); + } +} + +LONG TextField::Edit::ClipXCoordToVisibleText(LONG x, + bool is_triple_click) const { + // Clip the X coordinate to the left edge of the text. Careful: + // PosFromChar(0) may return a negative X coordinate if the beginning of the + // text has scrolled off the edit, so don't go past the clip rect's edge. + RECT r; + GetRect(&r); + const int left_bound = std::max(r.left, PosFromChar(0).x); + if (x < left_bound) + return left_bound; + + // See if we need to clip to the right edge of the text. + const int length = GetTextLength(); + // Asking for the coordinate of any character past the end of the text gets + // the pixel just to the right of the last character. + const int right_bound = std::min(r.right, PosFromChar(length).x); + if ((length == 0) || (x < right_bound)) + return x; + + // For trailing characters that are 2 pixels wide of less (like "l" in some + // fonts), we have a problem: + // * Clicks on any pixel within the character will place the cursor before + // the character. + // * Clicks on the pixel just after the character will not allow triple- + // click to work properly (true for any last character width). + // So, we move to the last pixel of the character when this is a + // triple-click, and moving to one past the last pixel in all other + // scenarios. This way, all clicks that can move the cursor will place it at + // the end of the text, but triple-click will still work. + return is_triple_click ? (right_bound - 1) : right_bound; +} + +void TextField::Edit::SetContainsMouse(bool contains_mouse) { + if (contains_mouse == contains_mouse_) + return; + + contains_mouse_ = contains_mouse; + + if (!draw_border_) + return; + + if (contains_mouse_) { + // Register for notification when the mouse leaves. Need to do this so + // that we can reset contains mouse properly. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = m_hWnd; + tme.dwHoverTime = 0; + TrackMouseEvent(&tme); + } + RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME); +} + +ITextDocument* TextField::Edit::GetTextObjectModel() const { + if (!text_object_model_) { + CComPtr<IRichEditOle> ole_interface; + ole_interface.Attach(GetOleInterface()); + text_object_model_ = ole_interface; + } + return text_object_model_; +} + +///////////////////////////////////////////////////////////////////////////// +// TextField + +TextField::~TextField() { + if (edit_) { + // If the edit hwnd still exists, we need to destroy it explicitly. + if (*edit_) + edit_->DestroyWindow(); + delete edit_; + } +} + +void TextField::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + ViewContainer* vc; + + if (is_add && (vc = GetViewContainer())) { + // This notification is called from the AddChildView call below. Ignore it. + if (native_view_ && !edit_) + return; + + if (!native_view_) { + native_view_ = new HWNDView(); // Deleted from our superclass destructor + AddChildView(native_view_); + + // Maps the focus of the native control to the focus of this view. + native_view_->SetAssociatedFocusView(this); + } + + // If edit_ is invalid from a previous use. Reset it. + if (edit_ && !IsWindow(edit_->m_hWnd)) { + native_view_->Detach(); + delete edit_; + edit_ = NULL; + } + + if (!edit_) { + edit_ = new Edit(this, draw_border_); + edit_->SetFont(font_.hfont()); + native_view_->Attach(*edit_); + if (!text_.empty()) + edit_->SetText(text_); + if (!use_default_background_color_) + SetBackgroundColor(background_color_); + Layout(); + } + } else if (!is_add && edit_ && IsWindow(edit_->m_hWnd)) { + edit_->SetParent(NULL); + } +} + +void TextField::Layout() { + if (native_view_) { + CRect lb; + GetLocalBounds(&lb, true); + native_view_->SetBounds(0, 0, lb.Width(), lb.Height()); + native_view_->UpdateHWNDBounds(); + } +} + +void TextField::DidChangeBounds(const CRect& previous, const CRect& current) { + Layout(); +} + +void TextField::GetPreferredSize(CSize *out) { + gfx::Insets insets; + CalculateInsets(&insets); + out->cx = default_width_in_chars_ * font_.ave_char_width() + insets.width(); + out->cy = num_lines_ * font_.height() + insets.height(); +} + +std::wstring TextField::GetText() const { + return text_; +} + +void TextField::SetText(const std::wstring& text) { + text_ = text; + if (edit_) + edit_->SetText(text); +} + +void TextField::CalculateInsets(gfx::Insets* insets) { + DCHECK(insets); + + if (!draw_border_) + return; + + // NOTE: One would think GetThemeMargins would return the insets we should + // use, but it doesn't. The margins returned by GetThemeMargins are always + // 0. + + // This appears to be the insets used by Windows. + insets->Set(3, 3, 3, 3); +} + +void TextField::SyncText() { + if (edit_) + text_ = edit_->GetText(); +} + +void TextField::SetController(Controller* controller) { + controller_ = controller; +} + +TextField::Controller* TextField::GetController() const { + return controller_; +} + +bool TextField::IsReadOnly() const { + return edit_ ? ((edit_->GetStyle() & ES_READONLY) != 0) : read_only_; +} + +bool TextField::IsMultiLine() const { + return (style_ & STYLE_MULTILINE) != 0; +} + +void TextField::SetReadOnly(bool read_only) { + if (edit_) + edit_->SetReadOnly(read_only); + else + read_only_ = read_only; +} + + +void TextField::Focus() { + ::SetFocus(native_view_->GetHWND()); +} + +void TextField::SelectAll() { + if (edit_) + edit_->SelectAll(); +} + +void TextField::ClearSelection() const { + if (edit_) + edit_->ClearSelection(); +} + +HWND TextField::GetNativeComponent() { + return native_view_->GetHWND(); +} + +void TextField::SetBackgroundColor(SkColor color) { + background_color_ = color; + use_default_background_color_ = false; + if (edit_) { + edit_->SetBackgroundColor(RGB(SkColorGetR(color), + SkColorGetG(color), + SkColorGetB(color))); + } +} + +void TextField::SetDefaultBackgroundColor() { + use_default_background_color_ = true; + if (edit_) + edit_->SetBackgroundColor(); +} + +void TextField::SetFont(const ChromeFont& font) { + font_ = font; + if (edit_) + edit_->SetFont(font.hfont()); +} + +ChromeFont TextField::GetFont() const { + return font_; +} + +bool TextField::SetHorizontalMargins(int left, int right) { + // SendMessage expects the two values to be packed into one using MAKELONG + // so we truncate to 16 bits if necessary. + return ERROR_SUCCESS == SendMessage(GetNativeComponent(), + (UINT) EM_SETMARGINS, + (WPARAM) EC_LEFTMARGIN | EC_RIGHTMARGIN, + (LPARAM) MAKELONG(left & 0xFFFF, + right & 0xFFFF)); +} + +void TextField::SetHeightInLines(int num_lines) { + DCHECK(IsMultiLine()); + num_lines_ = num_lines; +} + +void TextField::RemoveBorder() { + if (!draw_border_) + return; + + draw_border_ = false; + if (edit_) + edit_->RemoveBorder(); +} + +void TextField::SetEnabled(bool enabled) { + View::SetEnabled(enabled); + SetReadOnly(enabled); + edit_->SetEnabled(enabled); +} + +bool TextField::IsFocusable() const { + return IsEnabled() && !IsReadOnly(); +} + +void TextField::AboutToRequestFocusFromTabTraversal(bool reverse) { + SelectAll(); +} + +// We don't translate accelerators for ALT + numpad digit, they are used for +// entering special characters. +bool TextField::ShouldLookupAccelerators(const KeyEvent& e) { + if (!e.IsAltDown()) + return true; + + return !win_util::IsNumPadDigit(e.GetCharacter(), e.IsExtendedKey()); +} + +} // namespace ChromeViews diff --git a/chrome/views/text_field.h b/chrome/views/text_field.h new file mode 100644 index 0000000..86aedca --- /dev/null +++ b/chrome/views/text_field.h @@ -0,0 +1,222 @@ +// 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. + +// These classes define a text field widget that can be used in the ChromeViews +// UI toolkit. + +#ifndef CHROME_VIEWS_TEXT_FIELD_H__ +#define CHROME_VIEWS_TEXT_FIELD_H__ + +#include <string> + +#include "base/basictypes.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/view.h" +#include "skia/include/SkColor.h" + +namespace ChromeViews { + +class HWNDView; + +// This class implements a ChromeView that wraps a native text (edit) field. +class TextField : public View { + public: + // This defines the callback interface for other code to be notified of + // changes in the state of a text field. + class Controller { + public: + // This method is called whenever the text in the field changes. + virtual void ContentsChanged(TextField* sender, + const std::wstring& new_contents) = 0; + + // This method is called to get notified about keystrokes in the edit. + virtual void HandleKeystroke(TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags) = 0; + }; + + enum StyleFlags { + STYLE_DEFAULT = 0, + STYLE_PASSWORD = 1<<0, + STYLE_MULTILINE = 1<<1 + }; + + TextField::TextField() + : native_view_(NULL), + edit_(NULL), + controller_(NULL), + style_(STYLE_DEFAULT), + read_only_(false), + default_width_in_chars_(0), + draw_border_(true), + use_default_background_color_(true), + num_lines_(1) { + SetFocusable(true); + } + explicit TextField::TextField(StyleFlags style) + : native_view_(NULL), + edit_(NULL), + controller_(NULL), + style_(style), + read_only_(false), + default_width_in_chars_(0), + draw_border_(true), + use_default_background_color_(true), + num_lines_(1) { + SetFocusable(true); + } + virtual ~TextField(); + + void ViewHierarchyChanged(bool is_add, View* parent, View* child); + + // Overridden for layout purposes + virtual void Layout(); + virtual void GetPreferredSize(CSize* sz); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Controller accessors + void SetController(Controller* controller); + Controller* GetController() const; + + void SetReadOnly(bool read_only); + bool IsReadOnly() const; + + // Whether the text field is multi-line or not, must be set when the text + // field is created, using StyleFlags. + bool IsMultiLine() const; + + virtual bool IsFocusable() const; + virtual void AboutToRequestFocusFromTabTraversal(bool reverse); + + // Overridden from Chrome::View. + virtual bool ShouldLookupAccelerators(const KeyEvent& e); + + virtual HWND GetNativeComponent(); + + // Returns the text currently displayed in the text field. + std::wstring GetText() const; + + // Set the text currently displayed in the text field. + void SetText(const std::wstring& text); + + virtual void Focus(); + + // Causes the edit field to be fully selected. + void SelectAll(); + + // Clears the selection within the edit field and sets the caret to the end. + void ClearSelection() const; + + StyleFlags GetStyle() const { return style_; } + + void SetBackgroundColor(SkColor color); + void SetDefaultBackgroundColor(); + + // Set the font. + void SetFont(const ChromeFont& font); + + // Return the font used by this TextField. + ChromeFont GetFont() const; + + // Sets the left and right margin (in pixels) within the text box. On Windows + // this is accomplished by packing the left and right margin into a single + // 32 bit number, so the left and right margins are effectively 16 bits. + bool SetHorizontalMargins(int left, int right); + + // Should only be called on a multi-line text field. Sets how many lines of + // text can be displayed at once by this text field. + void SetHeightInLines(int num_lines); + + // Sets the default width of the text control. See default_width_in_chars_. + void set_default_width_in_chars(int default_width) { + default_width_in_chars_ = default_width; + } + + // Removes the border from the edit box, giving it a 2D look. + void RemoveBorder(); + + // Disable the edit control. + void SetEnabled(bool enabled); + + private: + class Edit; + + // Invoked by the edit control when the value changes. This method set + // the text_ member variable to the value contained in edit control. + // This is important because the edit control can be replaced if it has + // been deleted during a window close. + void SyncText(); + + // Reset the text field native control. + void ResetNativeControl(); + + // This encapsulates the HWND of the native text field. + HWNDView* native_view_; + + // This inherits from the native text field. + Edit* edit_; + + // This is the current listener for events from this control. + Controller* controller_; + + StyleFlags style_; + + ChromeFont font_; + + // NOTE: this is temporary until we rewrite TextField to always work whether + // there is an HWND or not. + // Used if the HWND hasn't been created yet. + std::wstring text_; + + bool read_only_; + + // The default number of average characters for the width of this text field. + // This will be reported as the "desired size". Defaults to 0. + int default_width_in_chars_; + + // Whether the border is drawn. + bool draw_border_; + + SkColor background_color_; + + bool use_default_background_color_; + + // The number of lines of text this textfield displays at once. + int num_lines_; + + protected: + // Calculates the insets for the text field. + void CalculateInsets(gfx::Insets* insets); + + DISALLOW_EVIL_CONSTRUCTORS(TextField); +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_TEXT_FIELD_H__ diff --git a/chrome/views/throbber.cc b/chrome/views/throbber.cc new file mode 100644 index 0000000..e30fe4c --- /dev/null +++ b/chrome/views/throbber.cc @@ -0,0 +1,218 @@ +// 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 "chrome/views/throbber.h" + +#include "base/message_loop.h" +#include "base/timer.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/resource_bundle.h" +#include "skia/include/SkBitmap.h" + +namespace ChromeViews { + +Throbber::Throbber(int frame_time_ms, + bool paint_while_stopped) + : paint_while_stopped_(paint_while_stopped), + running_(false), + last_frame_drawn_(-1), + frame_time_ms_(frame_time_ms), + frames_(NULL), + last_time_recorded_(0) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + frames_ = rb.GetBitmapNamed(IDR_THROBBER); + DCHECK(frames_->width() > 0 && frames_->height() > 0); + DCHECK(frames_->width() % frames_->height() == 0); + frame_count_ = frames_->width() / frames_->height(); +} + +Throbber::~Throbber() { + Stop(); +} + +void Throbber::Start() { + if (running_) + return; + + start_time_ = GetTickCount(); + last_time_recorded_ = start_time_; + + timer_ = MessageLoop::current()->timer_manager()->StartTimer( + frame_time_ms_ - 10, this, true); + + running_ = true; + + SchedulePaint(); // paint right away +} + +void Throbber::Stop() { + if (!running_) + return; + + MessageLoop::current()->timer_manager()->StopTimer(timer_); + timer_ = NULL; + + running_ = false; + SchedulePaint(); // Important if we're not painting while stopped +} + +void Throbber::Run() { + DCHECK(running_); + + SchedulePaint(); +} + +void Throbber::GetPreferredSize(CSize *out) { + DCHECK(out); + + out->SetSize(frames_->height(), frames_->height()); +} + +void Throbber::Paint(ChromeCanvas* canvas) { + if (!running_ && !paint_while_stopped_) + return; + + DWORD current_time = GetTickCount(); + int current_frame = 0; + + // deal with timer wraparound + if (current_time < last_time_recorded_) { + start_time_ = current_time; + current_frame = (last_frame_drawn_ + 1) % frame_count_; + } else { + current_frame = + ((current_time - start_time_) / frame_time_ms_) % frame_count_; + } + + last_time_recorded_ = current_time; + last_frame_drawn_ = current_frame; + + int image_size = frames_->height(); + int image_offset = current_frame * image_size; + canvas->DrawBitmapInt(*frames_, + image_offset, 0, image_size, image_size, + 0, 0, image_size, image_size, + false); +} + + + +// Smoothed throbber --------------------------------------------------------- + + +// Delay after work starts before starting throbber, in milliseconds. +static const int kStartDelay = 200; + +// Delay after work stops before stopping, in milliseconds. +static const int kStopDelay = 50; + + +SmoothedThrobber::SmoothedThrobber(int frame_time_ms) + : Throbber(frame_time_ms, /* paint_while_stopped= */ false), + start_delay_factory_(this), + end_delay_factory_(this) { +} + +void SmoothedThrobber::Start() { + end_delay_factory_.RevokeAll(); + + if (!running_ && start_delay_factory_.empty()) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + start_delay_factory_.NewRunnableMethod( + &SmoothedThrobber::StartDelayOver), + kStartDelay); + } +} + +void SmoothedThrobber::StartDelayOver() { + Throbber::Start(); +} + +void SmoothedThrobber::Stop() { + TimerManager* timer_manager = MessageLoop::current()->timer_manager(); + + if (!running_) + start_delay_factory_.RevokeAll(); + + end_delay_factory_.RevokeAll(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + end_delay_factory_.NewRunnableMethod(&SmoothedThrobber::StopDelayOver), + kStopDelay); +} + +void SmoothedThrobber::StopDelayOver() { + Throbber::Stop(); +} + +// Checkmark throbber --------------------------------------------------------- + +CheckmarkThrobber::CheckmarkThrobber() + : checked_(false), + Throbber(kFrameTimeMs, false) { + InitClass(); +} + +void CheckmarkThrobber::SetChecked(bool checked) { + bool changed = checked != checked_; + if (changed) { + checked_ = checked; + SchedulePaint(); + } +} + +void CheckmarkThrobber::Paint(ChromeCanvas* canvas) { + if (running_) { + // Let the throbber throb... + Throbber::Paint(canvas); + return; + } + // Otherwise we paint our tick mark or nothing depending on our state. + if (checked_) { + int checkmark_x = (GetWidth() - checkmark_->width()) / 2; + int checkmark_y = (GetHeight() - checkmark_->height()) / 2; + canvas->DrawBitmapInt(*checkmark_, checkmark_x, checkmark_y); + } +} + +// static +void CheckmarkThrobber::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + checkmark_ = rb.GetBitmapNamed(IDR_INPUT_GOOD); + initialized = true; + } +} + +// static +SkBitmap* CheckmarkThrobber::checkmark_ = NULL; + +} // namespace ChromeViews diff --git a/chrome/views/throbber.h b/chrome/views/throbber.h new file mode 100644 index 0000000..e445285 --- /dev/null +++ b/chrome/views/throbber.h @@ -0,0 +1,142 @@ +// 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. + +// Throbbers display an animation, usually used as a status indicator. + +#ifndef CHROME_VIEWS_THROBBER_H__ +#define CHROME_VIEWS_THROBBER_H__ + +#include "base/basictypes.h" +#include "base/task.h" +#include "chrome/views/view.h" + +class SkBitmap; +class Timer; + +namespace ChromeViews { + +class Throbber : public ChromeViews::View, + public Task { + public: + // |frame_time_ms| is the amount of time that should elapse between frames + // (in milliseconds) + // If |paint_while_stopped| is false, this view will be invisible when not + // running. + Throbber(int frame_time_ms, bool paint_while_stopped); + virtual ~Throbber(); + + // Start and stop the throbber animation + virtual void Start(); + virtual void Stop(); + + // overridden from View + virtual void GetPreferredSize(CSize *out); + virtual void Paint(ChromeCanvas* canvas); + + // implemented from Task + virtual void Run(); + + protected: + // Specifies whether the throbber is currently animating or not + bool running_; + + private: + bool paint_while_stopped_; + int frame_count_; + int last_frame_drawn_; + DWORD start_time_; + DWORD last_time_recorded_; + SkBitmap* frames_; + int frame_time_ms_; + Timer* timer_; + + DISALLOW_EVIL_CONSTRUCTORS(Throbber); +}; + +// A SmoothedThrobber is a throbber that is representing potentially short +// and nonoverlapping bursts of work. SmoothedThrobber ignores small +// pauses in the work stops and starts, and only starts its throbber after +// a small amount of work time has passed. +class SmoothedThrobber : public Throbber { + public: + SmoothedThrobber(int frame_delay_ms); + + virtual void Start(); + virtual void Stop(); + + private: + // Called when the startup-delay timer fires + // This function starts the actual throbbing. + void StartDelayOver(); + + // Called when the shutdown-delay timer fires. + // This function stops the actual throbbing. + void StopDelayOver(); + + // Method factory for delaying throbber startup. + ScopedRunnableMethodFactory<SmoothedThrobber> start_delay_factory_; + // Method factory for delaying throbber shutdown. + ScopedRunnableMethodFactory<SmoothedThrobber> end_delay_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(SmoothedThrobber); +}; + +// A CheckmarkThrobber is a special variant of throbber that has three states: +// 1. not yet completed (which paints nothing) +// 2. working (which paints the throbber animation) +// 3. completed (which paints a checkmark) +// +class CheckmarkThrobber : public Throbber { + public: + CheckmarkThrobber(); + + // If checked is true, the throbber stops spinning and displays a checkmark. + // If checked is false, the throbber stops spinning and displays nothing. + void SetChecked(bool checked); + + // Overridden from ChromeViews::Throbber: + virtual void Paint(ChromeCanvas* canvas); + + private: + static const int kFrameTimeMs = 30; + + static void InitClass(); + + // Whether or not we should display a checkmark. + bool checked_; + + // The checkmark image. + static SkBitmap* checkmark_; + + DISALLOW_EVIL_CONSTRUCTORS(CheckmarkThrobber); +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_THROBBER_H__ diff --git a/chrome/views/tooltip_manager.cc b/chrome/views/tooltip_manager.cc new file mode 100644 index 0000000..3eac713 --- /dev/null +++ b/chrome/views/tooltip_manager.cc @@ -0,0 +1,366 @@ +// 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 <limits> + +#include "chrome/common/gfx/chrome_font.h" +#include "base/logging.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/win_util.h" +#include "chrome/views/root_view.h" +#include "chrome/views/tooltip_manager.h" +#include "chrome/views/view.h" +#include "chrome/views/view_container.h" + +namespace ChromeViews { + +//static +int TooltipManager::tooltip_height_ = 0; + +// Maximum number of lines we allow in the tooltip. +static const int kMaxLines = 6; + +// Breaks |text| along line boundaries, placing each line of text into lines. +static void SplitTooltipString(const std::wstring& text, + std::vector<std::wstring>* lines) { + size_t index = 0; + size_t next_index; + while ((next_index = text.find(TooltipManager::GetLineSeparator(), index)) + != std::wstring::npos && lines->size() < kMaxLines) { + lines->push_back(text.substr(index, next_index - index)); + index = next_index + TooltipManager::GetLineSeparator().size(); + } + if (next_index != text.size() && lines->size() < kMaxLines) + lines->push_back(text.substr(index, text.size() - index)); +} + +// static +int TooltipManager::GetTooltipHeight() { + DCHECK(tooltip_height_ > 0); + return tooltip_height_; +} + +static ChromeFont DetermineDefaultFont() { + HWND window = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, 0 , 0, 0, 0, 0, NULL, NULL, NULL, NULL); + HFONT hfont = reinterpret_cast<HFONT>(SendMessage(window, WM_GETFONT, 0, 0)); + ChromeFont font = hfont ? ChromeFont::CreateFont(hfont) : ChromeFont(); + DestroyWindow(window); + return font; +} + +// static +ChromeFont TooltipManager::GetDefaultFont() { + static ChromeFont* font = NULL; + if (!font) + font = new ChromeFont(DetermineDefaultFont()); + return *font; +} + +// static +const std::wstring& TooltipManager::GetLineSeparator() { + static const std::wstring* separator = NULL; + if (!separator) + separator = new std::wstring(L"\r\n"); + return *separator; +} + +TooltipManager::TooltipManager(ViewContainer* container, HWND parent) + : view_container_(container), + parent_(parent), + last_mouse_x_(-1), + last_mouse_y_(-1), + tooltip_showing_(false), + last_tooltip_view_(NULL), + last_view_out_of_sync_(false), + tooltip_width_(0) { + DCHECK(container && parent); + Init(); +} + +TooltipManager::~TooltipManager() { + if (tooltip_hwnd_) + DestroyWindow(tooltip_hwnd_); +} + +void TooltipManager::Init() { + // Create the tooltip control. + tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, + parent_, NULL, NULL, NULL); + + // This effectively turns off clipping of tooltips. We need this otherwise + // multi-line text (\r\n) won't work right. The size doesn't really matter + // (just as long as its bigger than the monitor's width) as we clip to the + // screen size before rendering. + SendMessage(tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, + std::numeric_limits<short>::max()); + + // Add one tool that is used for all tooltips. + toolinfo_.cbSize = sizeof(toolinfo_); + toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND; + toolinfo_.hwnd = parent_; + toolinfo_.uId = reinterpret_cast<UINT_PTR>(parent_); + // Setting this tells windows to call parent_ back (using a WM_NOTIFY + // message) for the actual tooltip contents. + toolinfo_.lpszText = LPSTR_TEXTCALLBACK; + SetRectEmpty(&toolinfo_.rect); + SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); +} + +void TooltipManager::UpdateTooltip() { + // Set last_view_out_of_sync_ to indicate the view is currently out of sync. + // This doesn't update the view under the mouse immediately as it may cause + // timing problems. + last_view_out_of_sync_ = true; + last_tooltip_view_ = NULL; + // Hide the tooltip. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); +} + +void TooltipManager::TooltipTextChanged(View* view) { + if (view == last_tooltip_view_) + UpdateTooltip(last_mouse_x_, last_mouse_y_); +} + +LRESULT TooltipManager::OnNotify(int w_param, NMHDR* l_param, bool* handled) { + *handled = false; + if (l_param->hwndFrom == tooltip_hwnd_) { + switch (l_param->code) { + case TTN_GETDISPINFO: { + if (last_view_out_of_sync_) { + // View under the mouse is out of sync, determine it now. + RootView* root_view = view_container_->GetRootView(); + last_tooltip_view_ = root_view->GetViewForPoint(CPoint(last_mouse_x_, last_mouse_y_)); + last_view_out_of_sync_ = false; + } + // Tooltip control is asking for the tooltip to display. + NMTTDISPINFOW* tooltip_info = + reinterpret_cast<NMTTDISPINFOW*>(l_param); + // Initialize the string, if we have a valid tooltip the string will + // get reset below. + tooltip_info->szText[0] = TEXT('\0'); + tooltip_text_.clear(); + tooltip_info->lpszText = NULL; + clipped_text_.clear(); + if (last_tooltip_view_ != NULL) { + tooltip_text_.clear(); + // Mouse is over a View, ask the View for it's tooltip. + CPoint view_loc(last_mouse_x_, last_mouse_y_); + View::ConvertPointToView(view_container_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipText(view_loc.x, view_loc.y, + &tooltip_text_) && + !tooltip_text_.empty()) { + // View has a valid tip, copy it into TOOLTIPINFO. + clipped_text_ = tooltip_text_; + TrimTooltipToFit(&clipped_text_, &tooltip_width_, &line_count_); + tooltip_info->lpszText = const_cast<WCHAR*>(clipped_text_.c_str()); + } else { + tooltip_text_.clear(); + } + } + *handled = true; + return 0; + } + case TTN_POP: + tooltip_showing_ = false; + *handled = true; + return 0; + case TTN_SHOW: { + *handled = true; + tooltip_showing_ = true; + // The tooltip is about to show, allow the view to position it + CPoint text_origin; + if (tooltip_height_ == 0) + tooltip_height_ = CalcTooltipHeight(); + CPoint view_loc(last_mouse_x_, last_mouse_y_); + View::ConvertPointToView(view_container_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipTextOrigin( + view_loc.x, view_loc.y, &text_origin) && + SetTooltipPosition(text_origin.x, text_origin.y)) { + // Return true, otherwise the rectangle we specified is ignored. + return TRUE; + } + return 0; + } + default: + // Fall through. + break; + } + } + return 0; +} + +bool TooltipManager::SetTooltipPosition(int text_x, int text_y) { + // NOTE: this really only tests that the y location fits on screen, but that + // is good enough for our usage. + + // Calculate the bounds the tooltip will get. + CPoint view_loc(0, 0); + View::ConvertPointToScreen(last_tooltip_view_, &view_loc); + RECT bounds = { view_loc.x + text_x, + view_loc.y + text_y, + view_loc.x + text_x + tooltip_width_, + view_loc.y + line_count_ * GetTooltipHeight() }; + SendMessage(tooltip_hwnd_, TTM_ADJUSTRECT, TRUE, (LPARAM)&bounds); + + // Make sure the rectangle completely fits on the current monitor. If it + // doesn't, return false so that windows positions the tooltip at the + // default location. + gfx::Rect monitor_bounds = + win_util::GetMonitorBoundsForRect(gfx::Rect(bounds.left,bounds.right, + 0, 0)); + if (!monitor_bounds.Contains(gfx::Rect(bounds))) { + return false; + } + + ::SetWindowPos(tooltip_hwnd_, NULL, bounds.left, bounds.top, 0, 0, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); + return true; +} + +int TooltipManager::CalcTooltipHeight() { + // Ask the tooltip for it's font. + int height; + HFONT hfont = reinterpret_cast<HFONT>( + SendMessage(tooltip_hwnd_, WM_GETFONT, 0, 0)); + if (hfont != NULL) { + HDC dc = GetDC(tooltip_hwnd_); + HFONT previous_font = static_cast<HFONT>(SelectObject(dc, hfont)); + int last_map_mode = SetMapMode(dc, MM_TEXT); + TEXTMETRIC font_metrics; + GetTextMetrics(dc, &font_metrics); + height = font_metrics.tmHeight; + // To avoid the DC referencing font_handle_, select the previous font. + SelectObject(dc, previous_font); + SetMapMode(dc, last_map_mode); + ReleaseDC(NULL, dc); + } else { + // Tooltip is using the system font. Use ChromeFont, which should pick + // up the system font. + height = ChromeFont().height(); + } + // Get the margins from the tooltip + RECT tooltip_margin; + SendMessage(tooltip_hwnd_, TTM_GETMARGIN, 0, (LPARAM)&tooltip_margin); + return height + tooltip_margin.top + tooltip_margin.bottom; +} + +void TooltipManager::TrimTooltipToFit(std::wstring* text, + int* max_width, + int* line_count) { + *max_width = 0; + *line_count = 0; + + // Determine the available width for the tooltip. + CPoint screen_loc(last_mouse_x_, last_mouse_y_); + View::ConvertPointToScreen(view_container_->GetRootView(), &screen_loc); + gfx::Rect monitor_bounds = + win_util::GetMonitorBoundsForRect(gfx::Rect(screen_loc.x, screen_loc.y, + 0, 0)); + RECT tooltip_margin; + SendMessage(tooltip_hwnd_, TTM_GETMARGIN, 0, (LPARAM)&tooltip_margin); + const int available_width = monitor_bounds.width() - tooltip_margin.left - + tooltip_margin.right; + if (available_width <= 0) + return; + + // Split the string. + std::vector<std::wstring> lines; + SplitTooltipString(*text, &lines); + *line_count = static_cast<int>(lines.size()); + + // Format each line to fit. + ChromeFont font = GetDefaultFont(); + std::wstring result; + for (std::vector<std::wstring>::iterator i = lines.begin(); i != lines.end(); + ++i) { + std::wstring elided_text = gfx::ElideText(*i, font, available_width); + *max_width = std::max(*max_width, font.GetStringWidth(elided_text)); + if (i == lines.begin() && i + 1 == lines.end()) { + *text = elided_text; + return; + } + if (!result.empty()) + result.append(GetLineSeparator()); + result.append(elided_text); + } + *text = result; +} + +void TooltipManager::UpdateTooltip(int x, int y) { + RootView* root_view = view_container_->GetRootView(); + View* view = root_view->GetViewForPoint(CPoint(x, y)); + if (view != last_tooltip_view_) { + // NOTE: This *must* be sent regardless of the visibility of the tooltip. + // It triggers Windows to ask for the tooltip again. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + last_tooltip_view_ = view; + } else if (last_tooltip_view_ != NULL) { + // Tooltip is showing, and mouse is over the same view. See if the tooltip + // text has changed. + CPoint view_point(x, y); + View::ConvertPointToView(root_view, last_tooltip_view_, &view_point); + std::wstring new_tooltip_text; + if (last_tooltip_view_->GetTooltipText(view_point.x, view_point.y, + &new_tooltip_text) && + new_tooltip_text != tooltip_text_) { + // The text has changed, hide the popup. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + if (!new_tooltip_text.empty() && tooltip_showing_) { + // New text is valid, show the popup. + SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); + } + } + } +} + +void TooltipManager::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + int x = GET_X_LPARAM(l_param); + int y = GET_Y_LPARAM(l_param); + if (u_msg != WM_MOUSEMOVE || last_mouse_x_ != x || last_mouse_y_ != y) { + last_mouse_x_ = x; + last_mouse_y_ = y; + UpdateTooltip(x, y); + } + // Forward the message onto the tooltip. + MSG msg; + msg.hwnd = parent_; + msg.message = u_msg; + msg.wParam = w_param; + msg.lParam = l_param; + SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, 0, (LPARAM)&msg); +} + +} // namespace ChromeViews diff --git a/chrome/views/tooltip_manager.h b/chrome/views/tooltip_manager.h new file mode 100644 index 0000000..c1457d4 --- /dev/null +++ b/chrome/views/tooltip_manager.h @@ -0,0 +1,170 @@ +// 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. + +#ifndef CHROME_VIEWS_TOOLTIP_MANAGER_H__ +#define CHROME_VIEWS_TOOLTIP_MANAGER_H__ + +#include <windows.h> +#include <string> +#include "base/basictypes.h" +#include "googleurl/src/gurl.h" + +class ChromeFont; + +namespace ChromeViews { + +class View; +class ViewContainer; + +// TooltipManager takes care of the wiring to support tooltips for Views. +// This class is intended to be used by ViewContainers. To use this, you must +// do the following: +// Add the following to your MSG_MAP: +// +// MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) +// MESSAGE_RANGE_HANDLER(WM_NCMOUSEMOVE, WM_NCMOUSEMOVE, OnMouseRange) +// MSG_WM_NOTIFY(OnNotify) +// +// With the following implementations: +// LRESULT XXX::OnMouseRange(UINT u_msg, WPARAM w_param, LPARAM l_param, +// BOOL& handled) { +// tooltip_manager_->OnMouse(u_msg, w_param, l_param); +// handled = FALSE; +// return 0; +// } +// +// LRESULT XXX::OnNotify(int w_param, NMHDR* l_param) { +// bool handled; +// LRESULT result = tooltip_manager_->OnNotify(w_param, l_param, &handled); +// SetMsgHandled(handled); +// return result; +// } +// +// And of course you'll need to create the TooltipManager! +// +// Lastly, you'll need to override GetTooltipManager. +// +// See XPFrame for an example of this in action. +class TooltipManager { + public: + // Returns the height of tooltips. This should only be invoked from within + // GetTooltipTextOrigin. + static int GetTooltipHeight(); + + // Returns the default font used by tooltips. + static ChromeFont GetDefaultFont(); + + // Returns the separator for lines of text in a tooltip. + static const std::wstring& GetLineSeparator(); + + // Creates a TooltipManager for the specified ViewContainer and parent window. + TooltipManager(ViewContainer* container, HWND parent); + virtual ~TooltipManager(); + + // Notification that the view hierarchy has changed in some way. + void UpdateTooltip(); + + // Invoked when the tooltip text changes for the specified views. + void TooltipTextChanged(View* view); + + // Message handlers. These forward to the tooltip control. + virtual void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param); + LRESULT OnNotify(int w_param, NMHDR* l_param, bool* handled); + + protected: + virtual void Init(); + + // Updates the tooltip for the specified location. + void UpdateTooltip(int x, int y); + + // Parent window the tooltip is added to. + HWND parent_; + + // Tooltip control window. + HWND tooltip_hwnd_; + + // Tooltip information. + TOOLINFO toolinfo_; + + // Last location of the mouse. This is in the coordinates of the rootview. + int last_mouse_x_; + int last_mouse_y_; + + // Whether or not the tooltip is showing. + bool tooltip_showing_; + + private: + // Sets the tooltip position based on the x/y position of the text. If the + // tooltip fits, true is returned. + bool SetTooltipPosition(int text_x, int text_y); + + // Calculates the preferred height for tooltips. This always returns a + // positive value. + int CalcTooltipHeight(); + + // Trims the tooltip to fit, setting text to the clipped result, width to the + // width (in pixels) of the clipped text and line_count to the number of lines + // of text in the tooltip. + void TrimTooltipToFit(std::wstring* text, + int* width, + int* line_count); + + // Hosting view container. + ViewContainer* view_container_; + + // The View the mouse is under. This is null if the mouse isn't under a + // View. + View* last_tooltip_view_; + + // Whether or not the view under the mouse needs to be refreshed. If this + // is true, when the tooltip is asked for the view under the mouse is + // refreshed. + bool last_view_out_of_sync_; + + // Text for tooltip from the view. + std::wstring tooltip_text_; + + // The clipped tooltip. + std::wstring clipped_text_; + + // Number of lines in the tooltip. + int line_count_; + + // Width of the last tooltip. + int tooltip_width_; + + // Height for a tooltip; lazily calculated. + static int tooltip_height_; + + DISALLOW_EVIL_CONSTRUCTORS(TooltipManager); +}; + +} // namespace ChromeViews + +#endif // CHROME_VIEWS_TOOLTIP_MANAGER_H__ diff --git a/chrome/views/tree_node_model.h b/chrome/views/tree_node_model.h new file mode 100644 index 0000000..420be2cf --- /dev/null +++ b/chrome/views/tree_node_model.h @@ -0,0 +1,296 @@ +// 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. + +#ifndef CHROME_VIEWS_TREE_NODE_MODEL_H__ +#define CHROME_VIEWS_TREE_NODE_MODEL_H__ + +#include <vector> + +#include "base/basictypes.h" +#include "chrome/common/scoped_vector.h" +#include "chrome/views/tree_view.h" + +namespace ChromeViews { + +// TreeNodeModel and TreeNodes provide an implementation of TreeModel around +// TreeNodes. TreeNodes form a directed acyclic graph. +// +// TreeNodes own their children, so that deleting a node deletes all +// descendants. +// +// TreeNodes do NOT maintain a pointer back to the model. As such, if you +// are using TreeNodes with a TreeNodeModel you will need to notify the observer +// yourself any time you make any change directly to the TreeNodes. For example, +// if you directly invoke SetTitle on a node it does not notify the +// observer, you will need to do it yourself. This includes the following +// methods: SetTitle, Remove and Add. TreeNodeModel provides cover +// methods that mutate the TreeNodes and notify the observer. If you are using +// TreeNodes with a TreeNodeModel use the cover methods to save yourself the +// headache. +// +// The following example creates a TreeNode with two children and then +// creates a TreeNodeModel from it: +// +// TreeNodeWithValue<int> root = new TreeNodeWithValue<int>(0, L"root"); +// root.add(new TreeNodeWithValue<int>(1, L"child 1")); +// root.add(new TreeNodeWithValue<int>(1, L"child 2")); +// TreeNodeModel<TreeNodeWithValue<int>>* model = +// new TreeNodeModel<TreeNodeWithValue<int>>(root); +// +// Two variants of TreeNode are provided here: +// +// . TreeNode itself is intended for subclassing. It has one type parameter +// that corresponds to the type of the node. When subclassing use your class +// name as the type parameter, eg: +// class MyTreeNode : public TreeNode<MyTreeNode> . +// . TreeNodeWithValue is a trivial subclass of TreeNode that has one type +// type parameter: a value type that is associated with the node. +// +// Which you use depends upon the situation. If you want to subclass and add +// methods, then use TreeNode. If you don't need any extra methods and just +// want to associate a value with each node, then use TreeNodeWithValue. +// +// Regardless of which TreeNode you use, if you are using the nodes with a +// TreeView take care to notify the observer when mutating the nodes. + +template <class NodeType> +class TreeNodeModel; + +// TreeNode ------------------------------------------------------------------- + +template <class NodeType> +class TreeNode : public TreeModelNode { + public: + TreeNode() : parent_(NULL) { } + + explicit TreeNode(const std::wstring& title) + : title_(title), parent_(NULL) {} + + virtual ~TreeNode() { + } + + // Adds the specified child node. + virtual void Add(int index, NodeType* child) { + DCHECK(child && index >= 0 && index <= GetChildCount()); + // If the node has a parent, remove it from its parent. + NodeType* node_parent = child->GetParent(); + if (node_parent) + node_parent->Remove(node_parent->IndexOfChild(child)); + child->parent_ = static_cast<NodeType*>(this); + children_->insert(children_->begin() + index, child); + } + + // Removes the node by index. This does NOT delete the specified node, it is + // up to the caller to delete it when done. + virtual NodeType* Remove(int index) { + DCHECK(index >= 0 && index < GetChildCount()); + NodeType* node = GetChild(index); + node->parent_ = NULL; + children_->erase(index + children_->begin()); + return node; + } + + // Returns the children. + std::vector<NodeType*> GetChildren() { + return children_->v; + } + + // Returns the number of children. + int GetChildCount() { + return static_cast<int>(children_->size()); + } + + // Returns a child by index. + NodeType* GetChild(int index) { + DCHECK(index >= 0 && index < GetChildCount()); + return children_[index]; + } + + // Returns the parent. + NodeType* GetParent() { + return parent_; + } + + // Returns the index of the specified child, or -1 if node is a not a child. + int IndexOfChild(const NodeType* node) { + DCHECK(node); + std::vector<NodeType*>::iterator i = + find(children_->begin(), children_->end(), node); + if (i != children_->end()) + return static_cast<int>(i - children_->begin()); + return -1; + } + + // Sets the title of the node. + void SetTitle(const std::wstring& string) { + title_ = string; + } + + // Returns the title of the node. + std::wstring GetTitle() { + return title_; + } + + // Returns true if this is the root. + bool IsRoot() { return (parent_ == NULL); } + + // Returns true if this == ancestor, or one of this nodes parents is + // ancestor. + bool HasAncestor(NodeType* ancestor) const { + if (ancestor == this) + return true; + if (!ancestor) + return false; + return parent_ ? parent_->HasAncestor(ancestor) : false; + } + + private: + // Title displayed in the tree. + std::wstring title_; + + NodeType* parent_; + + // Children. + ScopedVector<NodeType> children_; + + DISALLOW_EVIL_CONSTRUCTORS(TreeNode); +}; + +// TreeNodeWithValue ---------------------------------------------------------- + +template <class ValueType> +class TreeNodeWithValue : public TreeNode<TreeNodeWithValue<ValueType>> { + public: + TreeNodeWithValue() { } + + TreeNodeWithValue(const ValueType& value) + : TreeNode(std::wstring()), value(value) { } + + TreeNodeWithValue(const std::wstring& title, const ValueType& value) + : TreeNode(title), value(value) { } + + ValueType value; + + private: + DISALLOW_EVIL_CONSTRUCTORS(TreeNodeWithValue); +}; + +// TreeNodeModel -------------------------------------------------------------- + +// TreeModel implementation intended to be used with TreeNodes. +template <class NodeType> +class TreeNodeModel : public TreeModel { + public: + // Creates a TreeNodeModel with the specified root node. The root is owned + // by the TreeNodeModel. + explicit TreeNodeModel(NodeType* root) + : observer_(NULL), + root_(root) { + } + + virtual ~TreeNodeModel() {} + + virtual void SetObserver(TreeModelObserver* observer) { + observer_ = observer; + } + + TreeModelObserver* GetObserver() { + return observer_; + } + + // TreeModel methods, all forward to the nodes. + virtual NodeType* GetRoot() { return root_.get(); } + + virtual int GetChildCount(TreeModelNode* parent) { + DCHECK(parent); + return AsNode(parent)->GetChildCount(); + } + + virtual NodeType* GetChild(TreeModelNode* parent, int index) { + DCHECK(parent); + return AsNode(parent)->GetChild(index); + } + + virtual TreeModelNode* GetParent(TreeModelNode* node) { + DCHECK(node); + return AsNode(node)->GetParent(); + } + + NodeType* AsNode(TreeModelNode* model_node) { + return reinterpret_cast<NodeType*>(model_node); + } + + // Sets the title of the specified node. + virtual void SetTitle(TreeModelNode* node, + const std::wstring& title) { + DCHECK(node); + AsNode(node)->SetTitle(title); + NotifyObserverTreeNodeChanged(node); + } + + void Add(NodeType* parent, int index, NodeType* child) { + DCHECK(parent && child); + parent->Add(index, child); + NotifyObserverTreeNodesAdded(parent, index, 1); + } + + NodeType* Remove(NodeType* parent, int index) { + DCHECK(parent); + parent->Remove(index); + NotifyObserverTreeNodesRemoved(parent, index, 1); + } + + void NotifyObserverTreeNodesAdded(NodeType* parent, int start, int count) { + if (observer_) + observer_->TreeNodesAdded(this, parent, start, count); + } + + void NotifyObserverTreeNodesRemoved(NodeType* parent, int start, int count) { + if (observer_) + observer_->TreeNodesRemoved(this, parent, start, count); + } + + virtual void NotifyObserverTreeNodeChanged(TreeModelNode* node) { + if (observer_) + observer_->TreeNodeChanged(this, node); + } + + private: + // The root. + scoped_ptr<NodeType> root_; + + // The observer. + TreeModelObserver* observer_; + + DISALLOW_EVIL_CONSTRUCTORS(TreeNodeModel); +}; + +} // namespace + +#endif // CHROME_VIEWS_TREE_NODE_MODEL_H__ diff --git a/chrome/views/tree_view.cc b/chrome/views/tree_view.cc new file mode 100644 index 0000000..f4e43e1 --- /dev/null +++ b/chrome/views/tree_view.cc @@ -0,0 +1,616 @@ +// 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 "chrome/views/tree_view.h" + +#include <shellapi.h> + +#include "base/win_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/icon_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/views/focus_manager.h" + +namespace ChromeViews { + +static HIMAGELIST tree_image_list_ = NULL; + +// Creates the default image list used for trees. The image list is populated +// from the shell's icons. +static HIMAGELIST CreateDefaultImageList(bool rtl) { + SkBitmap* closed_icon = + ResourceBundle::GetSharedInstance().GetBitmapNamed( + (rtl ? IDR_FOLDER_CLOSED_RTL : IDR_FOLDER_CLOSED)); + SkBitmap* opened_icon = + ResourceBundle::GetSharedInstance().GetBitmapNamed( + (rtl ? IDR_FOLDER_OPEN_RTL : IDR_FOLDER_OPEN)); + int width = closed_icon->width(); + int height = closed_icon->height(); + DCHECK(opened_icon->width() == width && opened_icon->height() == height); + HIMAGELIST image_list = ImageList_Create(width, height, ILC_COLOR32, 2, 2); + if (image_list) { + // NOTE: the order the images are added in effects the selected + // image index when adding items to the tree. If you change the + // order you'll undoubtedly need to update itemex.iSelectedImage + // when the item is added. + HICON h_closed_icon = IconUtil::CreateHICONFromSkBitmap(*closed_icon); + HICON h_opened_icon = IconUtil::CreateHICONFromSkBitmap(*opened_icon); + ImageList_AddIcon(image_list, h_closed_icon); + ImageList_AddIcon(image_list, h_opened_icon); + DestroyIcon(h_closed_icon); + DestroyIcon(h_opened_icon); + } + return image_list; +} + +TreeView::TreeView() + : tree_view_(NULL), + model_(NULL), + editable_(true), + next_id_(0), + controller_(NULL), + editing_node_(NULL), + root_shown_(true), + process_enter_(false), + show_context_menu_only_when_node_selected_(true), + select_on_right_mouse_down_(true), + wrapper_(this), + original_handler_(NULL) { +} + +TreeView::~TreeView() { + if (model_) + model_->SetObserver(NULL); + // Both param_to_details_map_ and node_to_details_map_ have the same value, + // as such only need to delete from one. + STLDeleteContainerPairSecondPointers(id_to_details_map_.begin(), + id_to_details_map_.end()); +} + +void TreeView::SetModel(TreeModel* model) { + if (model == model_) + return; + if(model_ && tree_view_) + DeleteRootItems(); + if (model_) + model_->SetObserver(NULL); + model_ = model; + if (tree_view_ && model_) { + CreateRootItems(); + model_->SetObserver(this); + } +} + +// Sets whether the user can edit the nodes. The default is true. +void TreeView::SetEditable(bool editable) { + if (editable == editable_) + return; + editable_ = editable; + if (!tree_view_) + return; + LONG_PTR style = GetWindowLongPtr(tree_view_, GWL_STYLE); + style &= ~TVS_EDITLABELS; + SetWindowLongPtr(tree_view_, GWL_STYLE, style); +} + +void TreeView::StartEditing(TreeModelNode* node) { + DCHECK(node && tree_view_); + // Cancel the current edit. + CancelEdit(); + // Make sure all ancestors are expanded. + if (model_->GetParent(node)) + Expand(model_->GetParent(node)); + const NodeDetails* details = GetNodeDetails(node); + // Tree needs focus for editing to work. + SetFocus(tree_view_); + // Select the node, else if the user commits the edit the selection reverts. + SetSelectedNode(node); + TreeView_EditLabel(tree_view_, details->tree_item); +} + +void TreeView::CancelEdit() { + DCHECK(tree_view_); + TreeView_EndEditLabelNow(tree_view_, TRUE); +} + +void TreeView::CommitEdit() { + DCHECK(tree_view_); + TreeView_EndEditLabelNow(tree_view_, FALSE); +} + +TreeModelNode* TreeView::GetEditingNode() { + // I couldn't find a way to dynamically query for this, so it is cached. + return editing_node_; +} + +void TreeView::SetSelectedNode(TreeModelNode* node) { + DCHECK(tree_view_); + if (!node) { + TreeView_SelectItem(tree_view_, NULL); + return; + } + if (node != model_->GetRoot()) + Expand(model_->GetParent(node)); + if (!root_shown_ && node == model_->GetRoot()) { + // If the root isn't shown, we can't select it, clear out the selection + // instead. + TreeView_SelectItem(tree_view_, NULL); + } else { + // Select the node and make sure it is visible. + TreeView_SelectItem(tree_view_, GetNodeDetails(node)->tree_item); + } +} + +TreeModelNode* TreeView::GetSelectedNode() { + if (!tree_view_) + return NULL; + HTREEITEM selected_item = TreeView_GetSelection(tree_view_); + if (!selected_item) + return NULL; + NodeDetails* details = GetNodeDetailsByTreeItem(selected_item); + DCHECK(details); + return details->node; +} + +void TreeView::Expand(TreeModelNode* node) { + DCHECK(model_ && node); + if (!root_shown_ && model_->GetRoot() == node) { + // Can only expand the root if it is showing. + return; + } + TreeModelNode* parent = model_->GetParent(node); + if (parent) { + // Make sure all the parents are expanded. + Expand(parent); + } + // And expand this item. + TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND); +} + +void TreeView::ExpandAll() { + DCHECK(model_); + ExpandAll(model_->GetRoot()); +} + +void TreeView::SetRootShown(bool root_shown) { + if (root_shown_ == root_shown) + return; + root_shown_ = root_shown; + if (!model_) + return; + // Repopulate the tree. + DeleteRootItems(); + CreateRootItems(); +} + +void TreeView::TreeNodesAdded(TreeModel* model, + TreeModelNode* parent, + int start, + int count) { + DCHECK(parent && start >= 0 && count > 0); + if (node_to_details_map_.find(parent) == node_to_details_map_.end()) { + // User hasn't navigated to this entry yet. Ignore the change. + return; + } + HTREEITEM parent_tree_item = NULL; + if (root_shown_ || parent != model_->GetRoot()) { + const NodeDetails* details = GetNodeDetails(parent); + if (!details->loaded_children) { + if (count == model_->GetChildCount(parent)) { + // Reset the treeviews child count. This triggers the treeview to call + // us back. + TV_ITEM tv_item = {0}; + tv_item.mask = TVIF_CHILDREN; + tv_item.cChildren = count; + tv_item.hItem = details->tree_item; + TreeView_SetItem(tree_view_, &tv_item); + } + + // Ignore the change, we haven't actually created entries in the tree + // for the children. + return; + } + parent_tree_item = details->tree_item; + } + + // The user has expanded this node, add the items to it. + for (int i = 0; i < count; ++i) { + if (i == 0 && start == 0) { + CreateItem(parent_tree_item, TVI_FIRST, model_->GetChild(parent, 0)); + } else { + TreeModelNode* previous_sibling = model_->GetChild(parent, i + start - 1); + CreateItem(parent_tree_item, + GetNodeDetails(previous_sibling)->tree_item, + model_->GetChild(parent, i + start)); + } + } +} + +void TreeView::TreeNodesRemoved(TreeModel* model, + TreeModelNode* parent, + int start, + int count) { + DCHECK(parent && start >= 0 && count > 0); + if (node_to_details_map_.find(parent) == node_to_details_map_.end()) { + // User hasn't navigated to this entry yet. Ignore the change. + return; + } + HTREEITEM parent_tree_item = NULL; + if (!root_shown_ || parent != model_->GetRoot()) { + const NodeDetails* details = GetNodeDetails(parent); + if (!details->loaded_children) { + // Ignore the change, we haven't actually created entries in the tree + // for the children. + return; + } + parent_tree_item = details->tree_item; + } else { + parent_tree_item = TreeView_GetRoot(tree_view_); + } + // Find the last item. Windows doesn't offer a convenient way to get the + // TREEITEM at a particular index, so we iterate. + HTREEITEM tree_item = TreeView_GetChild(tree_view_, parent_tree_item); + for (int i = 0; i < (start + count - 1); ++i) { + tree_item = TreeView_GetNextSibling(tree_view_, tree_item); + } + // NOTE: the direction doesn't matter here. I've made it backwards to + // reinforce we're deleting from the end forward. + for (int i = count - 1; i >= 0; --i) { + HTREEITEM previous = (start + i) > 0 ? + TreeView_GetPrevSibling(tree_view_, tree_item) : NULL; + RecursivelyDelete(GetNodeDetailsByTreeItem(tree_item)); + tree_item = previous; + } +} + +void TreeView::TreeNodeChanged(TreeModel* model, TreeModelNode* node) { + if (node_to_details_map_.find(node) == node_to_details_map_.end()) { + // User hasn't navigated to this entry yet. Ignore the change. + return; + } + const NodeDetails* details = GetNodeDetails(node); + TV_ITEM tv_item = {0}; + tv_item.mask = TVIF_TEXT; + tv_item.hItem = details->tree_item; + tv_item.pszText = LPSTR_TEXTCALLBACK; + TreeView_SetItem(tree_view_, &tv_item); +} + +HWND TreeView::CreateNativeControl(HWND parent_container) { + int style = WS_CHILD | TVS_DISABLEDRAGDROP | TVS_HASBUTTONS | + TVS_HASLINES | TVS_SHOWSELALWAYS; + if (editable_) + style |= TVS_EDITLABELS; + tree_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(), + WC_TREEVIEW, + L"", + style, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + SetWindowLongPtr(tree_view_, GWLP_USERDATA, + reinterpret_cast<LONG_PTR>(&wrapper_)); + original_handler_ = win_util::SetWindowProc(tree_view_, + &TreeWndProc); + // Tree-View doesn't render icons by default. Use an image list that is + // populated with icons from the shell. + if (!tree_image_list_) + tree_image_list_ = CreateDefaultImageList(UILayoutIsRightToLeft()); + if (tree_image_list_) + TreeView_SetImageList(tree_view_, tree_image_list_, TVSIL_NORMAL); + + if (model_) { + CreateRootItems(); + model_->SetObserver(this); + } + + // Bug 964884: detach the IME attached to this window. + // We should attach IMEs only when we need to input CJK strings. + ::ImmAssociateContextEx(tree_view_, NULL, 0); + return tree_view_; +} + +LRESULT TreeView::OnNotify(int w_param, LPNMHDR l_param) { + switch (l_param->code) { + case TVN_GETDISPINFO: { + // Windows is requesting more information about an item. + // WARNING: At the time this is called the tree_item of the NodeDetails + // in the maps is NULL. + DCHECK(model_); + NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param); + const NodeDetails* details = + GetNodeDetailsByID(static_cast<int>(info->item.lParam)); + if (info->item.mask & TVIF_CHILDREN) + info->item.cChildren = model_->GetChildCount(details->node); + if (info->item.mask & TVIF_TEXT) { + std::wstring text = details->node->GetTitle(); + DCHECK(info->item.cchTextMax); + + // Adjust the string direction if such adjustment is required. + std::wstring localized_text; + if (l10n_util::AdjustStringForLocaleDirection(text, &localized_text)) + text.swap(localized_text); + + wcsncpy_s(info->item.pszText, info->item.cchTextMax, text.c_str(), + _TRUNCATE); + } + // Instructs windows to cache the values for this node. + info->item.mask |= TVIF_DI_SETITEM; + // Return value ignored. + return 0; + } + + case TVN_ITEMEXPANDING: { + // Notification that a node is expanding. If we haven't populated the + // tree view with the contents of the model, we do it here. + DCHECK(model_); + NMTREEVIEW* info = reinterpret_cast<NMTREEVIEW*>(l_param); + NodeDetails* details = + GetNodeDetailsByID(static_cast<int>(info->itemNew.lParam)); + if (!details->loaded_children) { + details->loaded_children = true; + for (int i = 0; i < model_->GetChildCount(details->node); ++i) + CreateItem(details->tree_item, TVI_LAST, + model_->GetChild(details->node, i)); + } + // Return FALSE to allow the item to be expanded. + return FALSE; + } + + case TVN_SELCHANGED: + if (controller_) + controller_->OnTreeViewSelectionChanged(this); + break; + + case TVN_BEGINLABELEDIT: { + NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param); + NodeDetails* details = + GetNodeDetailsByID(static_cast<int>(info->item.lParam)); + // Return FALSE to allow editing. + if (!controller_ || controller_->CanEdit(this, details->node)) { + editing_node_ = details->node; + return FALSE; + } + return TRUE; + } + + case TVN_ENDLABELEDIT: { + NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param); + if (info->item.pszText) { + // User accepted edit. + NodeDetails* details = + GetNodeDetailsByID(static_cast<int>(info->item.lParam)); + model_->SetTitle(details->node, info->item.pszText); + editing_node_ = NULL; + // Return FALSE so that the tree item doesn't change its text (if the + // model changed the value, it should have sent out notification which + // will have updated the value). + return FALSE; + } + editing_node_ = NULL; + // Return value ignored. + return 0; + } + + default: + break; + } + return 0; +} + +bool TreeView::OnKeyDown(int virtual_key_code) { + if (virtual_key_code == VK_F2) { + if (!GetEditingNode()) { + TreeModelNode* selected_node = GetSelectedNode(); + if (selected_node) + StartEditing(selected_node); + } + return true; + } else if (virtual_key_code == VK_RETURN && !process_enter_) { + ViewContainer* vc = GetViewContainer(); + DCHECK(vc); + FocusManager* fm = FocusManager::GetFocusManager(vc->GetHWND()); + DCHECK(fm); + Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code), + win_util::IsShiftPressed(), + win_util::IsCtrlPressed(), + win_util::IsAltPressed())); + fm->ProcessAccelerator(accelerator, true); + return true; + } + return false; +} + +void TreeView::OnContextMenu(const CPoint& location) { + if (GetContextMenuController()) { + if (location.x == -1 && location.y == -1) { + // Indicates the user pressed the context menu key. + bool valid_loc = false; + int x, y; + if (GetSelectedNode()) { + RECT bounds; + if (TreeView_GetItemRect(tree_view_, + GetNodeDetails(GetSelectedNode())->tree_item, + &bounds, TRUE)) { + x = bounds.left; + y = bounds.top + (bounds.bottom - bounds.top) / 2; + valid_loc = true; + } + } else if (show_context_menu_only_when_node_selected_) { + return; + } + if (!valid_loc) { + x = GetWidth() / 2; + y = GetHeight() / 2; + } + CPoint screen_loc(x, y); + ConvertPointToScreen(this, &screen_loc); + GetContextMenuController()->ShowContextMenu(this, screen_loc.x, + screen_loc.y, false); + } else if (!show_context_menu_only_when_node_selected_) { + GetContextMenuController()->ShowContextMenu(this, location.x, location.y, + true); + } else if (GetSelectedNode()) { + // Make sure the mouse is over the selected node. + TVHITTESTINFO hit_info; + CPoint local_loc(location); + ConvertPointToView(NULL, this, &local_loc); + hit_info.pt.x = local_loc.x; + hit_info.pt.y = local_loc.y; + HTREEITEM hit_item = TreeView_HitTest(tree_view_, &hit_info); + if (hit_item && + GetNodeDetails(GetSelectedNode())->tree_item == hit_item && + (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT | + TVHT_ONITEMINDENT)) != 0) { + GetContextMenuController()->ShowContextMenu(this, location.x, + location.y, true); + } + } + } +} + +void TreeView::ExpandAll(TreeModelNode* node) { + DCHECK(node); + // Expand the node. + if (node != model_->GetRoot() || root_shown_) + TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND); + // And recursively expand all the children. + for (int i = model_->GetChildCount(node) - 1; i >= 0; --i) { + TreeModelNode* child = model_->GetChild(node, i); + ExpandAll(child); + } +} + +void TreeView::DeleteRootItems() { + HTREEITEM root = TreeView_GetRoot(tree_view_); + if (root) { + if (root_shown_) { + RecursivelyDelete(GetNodeDetailsByTreeItem(root)); + } else { + HTREEITEM node; + while ((node = TreeView_GetChild(tree_view_, root))) { + RecursivelyDelete(GetNodeDetailsByTreeItem(node)); + } + } + } +} + +void TreeView::CreateRootItems() { + DCHECK(model_); + TreeModelNode* root = model_->GetRoot(); + if (root_shown_) { + CreateItem(NULL, TVI_LAST, root); + } else { + for (int i = 0; i < model_->GetChildCount(root); ++i) + CreateItem(NULL, TVI_LAST, model_->GetChild(root, i)); + } +} + +void TreeView::CreateItem(HTREEITEM parent_item, + HTREEITEM after, + TreeModelNode* node) { + DCHECK(node); + TVINSERTSTRUCT insert_struct = {0}; + insert_struct.hParent = parent_item; + insert_struct.hInsertAfter = after; + insert_struct.itemex.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_TEXT | + TVIF_SELECTEDIMAGE; + // Call us back for the text. + insert_struct.itemex.pszText = LPSTR_TEXTCALLBACK; + // And the number of children. + insert_struct.itemex.cChildren = I_CHILDRENCALLBACK; + // Index in the image list for the image when the node is selected. + insert_struct.itemex.iSelectedImage = 1; + int node_id = next_id_++; + insert_struct.itemex.lParam = node_id; + + // Invoking TreeView_InsertItem triggers OnNotify to be called. As such, + // we set the map entries before adding the item. + NodeDetails* node_details = new NodeDetails(node_id, node); + + node_to_details_map_[node] = node_details; + id_to_details_map_[node_id] = node_details; + + node_details->tree_item = TreeView_InsertItem(tree_view_, &insert_struct); +} + +void TreeView::RecursivelyDelete(NodeDetails* node) { + DCHECK(node); + HTREEITEM item = node->tree_item; + DCHECK(item); + + // Recurse through children. + for (HTREEITEM child = TreeView_GetChild(tree_view_, item); + child ; child = TreeView_GetNextSibling(tree_view_, child)) { + RecursivelyDelete(GetNodeDetailsByTreeItem(child)); + } + + TreeView_DeleteItem(tree_view_, item); + + // finally, it is safe to delete the data for this node. + id_to_details_map_.erase(node->id); + node_to_details_map_.erase(node->node); + delete node; +} + +TreeView::NodeDetails* TreeView::GetNodeDetailsByTreeItem(HTREEITEM tree_item) { + DCHECK(tree_view_ && tree_item); + TV_ITEM tv_item = {0}; + tv_item.hItem = tree_item; + tv_item.mask = TVIF_PARAM; + if (TreeView_GetItem(tree_view_, &tv_item)) + return GetNodeDetailsByID(static_cast<int>(tv_item.lParam)); + return NULL; +} + +LRESULT CALLBACK TreeView::TreeWndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param) { + TreeViewWrapper* wrapper = reinterpret_cast<TreeViewWrapper*>( + GetWindowLongPtr(window, GWLP_USERDATA)); + DCHECK(wrapper); + TreeView* tree = wrapper->tree_view; + if (message == WM_RBUTTONDOWN && tree->select_on_right_mouse_down_) { + TVHITTESTINFO hit_info; + hit_info.pt.x = GET_X_LPARAM(l_param); + hit_info.pt.y = GET_Y_LPARAM(l_param); + HTREEITEM hit_item = TreeView_HitTest(window, &hit_info); + if (hit_item && (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT | + TVHT_ONITEMINDENT)) != 0) + TreeView_SelectItem(tree->tree_view_, hit_item); + // Fall through and let the default handler process as well. + } + WNDPROC handler = tree->original_handler_; + DCHECK(handler); + return CallWindowProc(handler, window, message, w_param, l_param); +} + +} // namespace ChromeViews diff --git a/chrome/views/tree_view.h b/chrome/views/tree_view.h new file mode 100644 index 0000000..e16a92f --- /dev/null +++ b/chrome/views/tree_view.h @@ -0,0 +1,352 @@ +// 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. + +#ifndef CHROME_VIEWS_TREE_VIEW_H__ +#define CHROME_VIEWS_TREE_VIEW_H__ + +#include <map> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chrome/views/native_control.h" + +namespace ChromeViews { + +class TreeModel; +class TreeModelNode; +class TreeView; + +// Observer for the TreeModel. Notified of significant events to the model. +class TreeModelObserver { + public: + // Notification that nodes were added to the specified parent. + virtual void TreeNodesAdded(TreeModel* model, + TreeModelNode* parent, + int start, + int count) = 0; + + // Notification that nodes were removed from the specified parent. + virtual void TreeNodesRemoved(TreeModel* model, + TreeModelNode* parent, + int start, + int count) = 0; + + // Notification that the contents of a node has changed. + virtual void TreeNodeChanged(TreeModel* model, TreeModelNode* node) = 0; +}; + +// TreeModelNode -------------------------------------------------------------- + +// Type of class returned from the model. +class TreeModelNode { + public: + // Returns the title for the node. + virtual std::wstring GetTitle() = 0; +}; + +// TreeModel ------------------------------------------------------------------ + +// The model for TreeView. +class TreeModel { + public: + // Returns the root of the tree. This may or may not be shown in the tree, + // see SetRootShown for details. + virtual TreeModelNode* GetRoot() = 0; + + // Returns the number of children in the specified node. + virtual int GetChildCount(TreeModelNode* parent) = 0; + + // Returns the child node at the specified index. + virtual TreeModelNode* GetChild(TreeModelNode* parent, int index) = 0; + + // Returns the parent of a node, or NULL if node is the root. + virtual TreeModelNode* GetParent(TreeModelNode* node) = 0; + + // Sets the observer of the model. + virtual void SetObserver(TreeModelObserver* observer) = 0; + + // Sets the title of the specified node. + // This is only invoked if the node is editable and the user edits a node. + virtual void SetTitle(TreeModelNode* node, + const std::wstring& title) { + NOTREACHED(); + } +}; + +// TreeViewController --------------------------------------------------------- + +// Controller for the treeview. +class TreeViewController { + public: + // Notification that the selection of the tree view has changed. Use + // GetSelectedNode to find the current selection. + virtual void OnTreeViewSelectionChanged(TreeView* tree_view) = 0; + + // Returns true if the node can be edited. This is only used if the + // TreeView is editable. + virtual bool CanEdit(TreeView* tree_view, TreeModelNode* node) { + return true; + } +}; + +// TreeView ------------------------------------------------------------------- + +// TreeView displays hierarchical data as returned from a TreeModel. The user +// can expand, collapse and edit the items. A Controller may be attached to +// receive notification of selection changes and restrict editing. +class TreeView : public NativeControl, public TreeModelObserver { + public: + TreeView(); + virtual ~TreeView(); + + // Sets the model. TreeView does not take ownership of the model. + void SetModel(TreeModel* model); + + // Sets whether the user can edit the nodes. The default is true. If true, + // the Controller is queried to determine if a particular node can be edited. + void SetEditable(bool editable); + + // Edits the specified node. This cancels the current edit and expands + // all parents of node. + void StartEditing(TreeModelNode* node); + + // Cancels the current edit. Does nothing if not editing. + void CancelEdit(); + + // Commits the current edit. Does nothing if not editing. + void CommitEdit(); + + // If the user is editing a node, it is returned. If the user is not + // editing a node, NULL is returned. + TreeModelNode* GetEditingNode(); + + // Selects the specified node. This expands all the parents of node. + void SetSelectedNode(TreeModelNode* node); + + // Returns the selected node, or NULL if nothing is selected. + TreeModelNode* GetSelectedNode(); + + // Make sure node and all its parents are expanded. + void Expand(TreeModelNode* node); + + // Convenience to expand ALL nodes in the tree. + void ExpandAll(); + + // Sets whether the root is shown. If true, the root node of the tree is + // shown, if false only the children of the root are shown. The default is + // true. + void SetRootShown(bool root_visible); + + // TreeModelObserver methods. Don't call these directly, instead your model + // should notify the observer TreeView adds to it. + virtual void TreeNodesAdded(TreeModel* model, + TreeModelNode* parent, + int start, + int count); + virtual void TreeNodesRemoved(TreeModel* model, + TreeModelNode* parent, + int start, + int count); + virtual void TreeNodeChanged(TreeModel* model, TreeModelNode* node); + + // Sets the controller, which may be null. TreeView does not take ownership + // of the controller. + void SetController(TreeViewController* controller) { + controller_ = controller; + } + + // Sets whether enter is processed when not editing. If true, enter will + // expand/collapse the node. If false, enter is passed to the focus manager + // so that an enter accelerator can be enabled. The default is false. + // + // NOTE: Changing this has no effect after the hwnd has been created. + void SetProcessesEnter(bool process_enter) { + process_enter_ = process_enter; + } + bool GetProcessedEnter() { return process_enter_; } + + // Sets when the ContextMenuController is notified. If true, the + // ContextMenuController is only notified when a node is selected and the + // mouse is over a node. The default is true. + void SetShowContextMenuOnlyWhenNodeSelected(bool value) { + show_context_menu_only_when_node_selected_ = value; + } + bool GetShowContextMenuOnlyWhenNodeSelected() { + return show_context_menu_only_when_node_selected_; + } + + // If true, a right click selects the node under the mouse. The default + // is true. + void SetSelectOnRightMouseDown(bool value) { + select_on_right_mouse_down_ = value; + } + bool GetSelectOnRightMouseDown() { return select_on_right_mouse_down_; } + + protected: + + // Creates and configures the tree_view. + virtual HWND CreateNativeControl(HWND parent_container); + + // Invoked when the native control sends a WM_NOTIFY message to its parent. + // Handles a variety of potential TreeView messages. + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + // Yes, we want to be notified of key down for two reasons. To circumvent + // VK_ENTER from toggling the expaned state when processes_enter_ is false, + // and to have F2 start editting. + virtual bool NotifyOnKeyDown() const { return true; } + virtual bool OnKeyDown(int virtual_key_code); + + virtual void OnContextMenu(const CPoint& location); + + private: + // See notes in TableView::TableViewWrapper for why this is needed. + struct TreeViewWrapper { + explicit TreeViewWrapper(TreeView* view) : tree_view(view) { } + TreeView* tree_view; + }; + + // Internally used to track the state of nodes. NodeDetails are lazily created + // as the user expands nodes. + struct NodeDetails { + NodeDetails(int id, TreeModelNode* node) + : id(id), node(node), tree_item(NULL), loaded_children(false) {} + + // Unique identifier for the node. This corresponds to the lParam of + // the tree item. + const int id; + + // The node from the model. + TreeModelNode* node; + + // From the native TreeView. + // + // This should be treated as const, but can't due to timing in creating the + // entry. + HTREEITEM tree_item; + + // Whether the children have been loaded. + bool loaded_children; + }; + + // Invoked from ExpandAll(). Expands the supplied node and recursively + // invokes itself with all children. + void ExpandAll(TreeModelNode* node); + + // Deletes the root items from the treeview. This is used when the model + // changes. + void DeleteRootItems(); + + // Creates the root items in the treeview from the model. This is used when + // the model changes. + void CreateRootItems(); + + // Creates and adds an item to the treeview. parent_item identifies the + // parent and is null for root items. after dictates where among the + // children of parent_item the item is to be created. node is the node from + // the model. + void CreateItem(HTREEITEM parent_item, HTREEITEM after, TreeModelNode* node); + + // Removes entries from the map for item. This method will also + // remove the items from the TreeView because the process of + // deleting an item will send an TVN_GETDISPINFO message, consulting + // our internal map data. + void RecursivelyDelete(NodeDetails* node); + + // Returns the NodeDetails by node from the model. + NodeDetails* GetNodeDetails(TreeModelNode* node) { + DCHECK(node && + node_to_details_map_.find(node) != node_to_details_map_.end()); + return node_to_details_map_[node]; + } + + // Returns the NodeDetails by identifier (lparam of the HTREEITEM). + NodeDetails* GetNodeDetailsByID(int id) { + DCHECK(id_to_details_map_.find(id) != id_to_details_map_.end()); + return id_to_details_map_[id]; + } + + // Returns the NodeDetails by HTREEITEM. + NodeDetails* GetNodeDetailsByTreeItem(HTREEITEM tree_item); + + // The window function installed on the treeview. + static LRESULT CALLBACK TreeWndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // Handle to the tree window. + HWND tree_view_; + + // The model, may be null. + TreeModel* model_; + + // Maps from id to NodeDetails. + std::map<int,NodeDetails*> id_to_details_map_; + + // Maps from model entry to NodeDetails. + std::map<TreeModelNode*,NodeDetails*> node_to_details_map_; + + // Whether the user can edit the items. + bool editable_; + + // Next id to create. Any time an item is added this is incremented by one. + int next_id_; + + // The controller. + TreeViewController* controller_; + + // Node being edited. If null, not editing. + TreeModelNode* editing_node_; + + // Whether or not the root is shown in the tree. + bool root_shown_; + + // Whether enter should be processed by the tree when not editing. + bool process_enter_; + + // Whether we notify context menu controller only when mouse is over node + // and node is selected. + bool show_context_menu_only_when_node_selected_; + + // Whether the selection is changed on right mouse down. + bool select_on_right_mouse_down_; + + // A wrapper around 'this', used for subclassing the TreeView control. + TreeViewWrapper wrapper_; + + // Original handler installed on the TreeView. + WNDPROC original_handler_; + + DISALLOW_EVIL_CONSTRUCTORS(TreeView); +}; + +} // namespace ChromeViews + +#endif CHROME_VIEWS_TREE_VIEW_H__ diff --git a/chrome/views/view.cc b/chrome/views/view.cc new file mode 100644 index 0000000..8df8da5 --- /dev/null +++ b/chrome/views/view.cc @@ -0,0 +1,1710 @@ +// 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 "chrome/views/view.h" + +#include <algorithm> +#include <sstream> +#include <iostream> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/views/background.h" +#include "chrome/views/border.h" +#include "chrome/views/layout_manager.h" +#include "chrome/views/root_view.h" +#include "chrome/views/tooltip_manager.h" +#include "chrome/views/view_container.h" +#include "SkShader.h" + +namespace ChromeViews { + +// static +char View::kViewClassName[] = "chrome/views/View"; + +//////////////////////////////////////////////////////////////////////////////// +// +// A task used to automatically restore focus on the last focused floating view +// +//////////////////////////////////////////////////////////////////////////////// + +class RestoreFocusTask : public Task { + public: + explicit RestoreFocusTask(View* target) : view_(target) { + } + + ~RestoreFocusTask() {} + + void Cancel() { + view_ = NULL; + } + + void Run() { + if (view_) + view_->RestoreFloatingViewFocus(); + } + private: + // The target view. + View* view_; + + DISALLOW_EVIL_CONSTRUCTORS(RestoreFocusTask); +}; + +///////////////////////////////////////////////////////////////////////////// +// +// View - constructors, destructors, initialization +// +///////////////////////////////////////////////////////////////////////////// + +View::View() + : id_(0), + group_(-1), + bounds_(0,0,0,0), + parent_(NULL), + enabled_(true), + is_visible_(true), + focusable_(false), + background_(NULL), + accessibility_(NULL), + border_(NULL), + is_parent_owned_(true), + notify_when_visible_bounds_in_root_changes_(false), + registered_for_visible_bounds_notification_(false), + next_focusable_view_(NULL), + previous_focusable_view_(NULL), + should_restore_focus_(false), + restore_focus_view_task_(NULL), + context_menu_controller_(NULL), + drag_controller_(NULL), + ui_mirroring_is_enabled_for_rtl_languages_(true), + flip_canvas_on_paint_for_rtl_ui_(false) { +} + +View::~View() { + if (restore_focus_view_task_) + restore_focus_view_task_->Cancel(); + + int c = static_cast<int>(child_views_.size()); + while (--c >= 0) { + if (child_views_[c]->IsParentOwned()) + delete child_views_[c]; + else + child_views_[c]->SetParent(NULL); + } + if (background_) + delete background_; + if (border_) + delete border_; +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - sizing +// +///////////////////////////////////////////////////////////////////////////// + +void View::GetBounds(CRect* out, PositionMirroringSettings settings) const { + *out = bounds_; + + // If the parent uses an RTL UI layout and if we are asked to transform the + // bounds to their mirrored position if necessary, then we should shift the + // rectangle appropriately. + if (settings == APPLY_MIRRORING_TRANSFORMATION) { + out->MoveToX(MirroredX()); + } +} + +// GetY(), GetWidth() and GetHeight() are agnostic to the RTL UI layout of the +// parent view. GetX(), on the other hand, is not. +int View::GetX(PositionMirroringSettings settings) const { + if (settings == IGNORE_MIRRORING_TRANSFORMATION) { + return bounds_.left; + } + return MirroredX(); +} + +void View::SetBounds(const CRect& bounds) { + if (bounds.left == bounds_.left && + bounds.top == bounds_.top && + bounds.Width() == bounds_.Width() && + bounds.Height() == bounds_.Height()) { + return; + } + + CRect prev = bounds_; + bounds_ = bounds; + if (bounds_.right < bounds_.left) + bounds_.right = bounds_.left; + + if (bounds_.bottom < bounds_.top) + bounds_.bottom = bounds_.top; + + DidChangeBounds(prev, bounds_); + + RootView* root = GetRootView(); + if (root) { + bool size_changed = (prev.Width() != bounds_.Width() || + prev.Height() != bounds_.Height()); + bool position_changed = (prev.left != bounds_.left || + prev.top != bounds_.top); + if (size_changed || position_changed) + root->ViewBoundsChanged(this, size_changed, position_changed); + } +} + +void View::SetBounds(int x, int y, int width, int height) { + CRect tmp(x, y, x + width, y + height); + SetBounds(tmp); +} + +void View::GetLocalBounds(CRect* out, bool include_border) const { + if (include_border || border_ == NULL) { + out->left = 0; + out->top = 0; + out->right = GetWidth(); + out->bottom = GetHeight(); + } else { + gfx::Insets insets; + border_->GetInsets(&insets); + out->left = insets.left(); + out->top = insets.top(); + out->right = GetWidth() - insets.left(); + out->bottom = GetHeight() - insets.top(); + } +} + +void View::GetSize(CSize* sz) const { + sz->cx = GetWidth(); + sz->cy = GetHeight(); +} + +void View::GetPosition(CPoint* p) const { + p->x = GetX(APPLY_MIRRORING_TRANSFORMATION); + p->y = GetY(); +} + +void View::GetPreferredSize(CSize* out) { + if (layout_manager_.get()) { + layout_manager_->GetPreferredSize(this, out); + } else { + out->cx = out->cy = 0; + } +} + +void View::SizeToPreferredSize() { + CSize size; + GetPreferredSize(&size); + if ((size.cx != GetWidth()) || (size.cy != GetHeight())) + SetBounds(GetX(), GetY(), size.cx, size.cy); +} + +void View::GetMinimumSize(CSize* out) { + GetPreferredSize(out); +} + +int View::GetHeightForWidth(int w) { + if (layout_manager_.get()) + return layout_manager_->GetPreferredHeightForWidth(this, w); + + CSize size; + GetPreferredSize(&size); + return size.cy; +} + +void View::DidChangeBounds(const CRect& previous, const CRect& current) { +} + +void View::ScrollRectToVisible(int x, int y, int width, int height) { + View* parent = GetParent(); + + // We must take RTL UI mirroring into account when adjusting the position of + // the region. + if (parent) + parent->ScrollRectToVisible( + GetX(APPLY_MIRRORING_TRANSFORMATION) + x, GetY() + y, width, height); +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - layout +// +///////////////////////////////////////////////////////////////////////////// + +void View::Layout() { + // Layout child Views + if (layout_manager_.get()) { + layout_manager_->Layout(this); + SchedulePaint(); + } + + // Lay out contents of child Views + int child_count = GetChildViewCount(); + for (int i = 0; i < child_count; ++i) { + View* child = GetChildViewAt(i); + child->Layout(); + } +} + +LayoutManager* View::GetLayoutManager() const { + return layout_manager_.get(); +} + +void View::SetLayoutManager(LayoutManager* layout_manager) { + if (layout_manager_.get()) { + layout_manager_->Uninstalled(this); + } + layout_manager_.reset(layout_manager); + if (layout_manager_.get()) { + layout_manager_->Installed(this); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// View - Right-to-left UI layout +// +//////////////////////////////////////////////////////////////////////////////// + +inline int View::MirroredX() const { + View* parent = GetParent(); + if (parent && parent->UILayoutIsRightToLeft()) { + return parent->GetWidth() - bounds_.left - GetWidth(); + } + return bounds_.left; +} + +int View::MirroredLeftPointForRect(const gfx::Rect& bounds) const { + if (!UILayoutIsRightToLeft()) { + return bounds.x(); + } + return GetWidth() - bounds.x() - bounds.width(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// View - states +// +//////////////////////////////////////////////////////////////////////////////// + +bool View::IsEnabled() const { + return enabled_; +} + +void View::SetEnabled(bool state) { + if (enabled_ != state) { + enabled_ = state; + SchedulePaint(); + } +} + +bool View::IsFocusable() const { + return focusable_ && enabled_ && is_visible_; +} + +void View::SetFocusable(bool focusable) { + focusable_ = focusable; +} + +FocusManager* View::GetFocusManager() { + ViewContainer* container = GetViewContainer(); + if (!container) + return NULL; + + HWND hwnd = container->GetHWND(); + if (!hwnd) + return NULL; + + return ChromeViews::FocusManager::GetFocusManager(hwnd); +} + +bool View::HasFocus() { + RootView* root_view = GetRootView(); + FocusManager* focus_manager = GetFocusManager(); + if (focus_manager) + return focus_manager->GetFocusedView() == this; + return false; +} + +void View::SetHotTracked(bool flag) { +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - painting +// +///////////////////////////////////////////////////////////////////////////// + +void View::SchedulePaint(const CRect& r, bool urgent) { + if (!IsVisible()) { + return; + } + + if (parent_) { + // Translate the requested paint rect to the parent's coordinate system + // then pass this notification up to the parent. + CRect paint_rect(r); + CPoint p; + GetPosition(&p); + paint_rect.OffsetRect(p); + parent_->SchedulePaint(paint_rect, urgent); + } +} + +void View::SchedulePaint() { + CRect lb; + GetLocalBounds(&lb, true); + SchedulePaint(lb, false); +} + +void View::SchedulePaint(int x, int y, int w, int h) { + CRect r(x, y, x + w, y + h); + SchedulePaint(&r, false); +} + +void View::Paint(ChromeCanvas* canvas) { + PaintBackground(canvas); + PaintFocusBorder(canvas); + PaintBorder(canvas); +} + +void View::PaintBackground(ChromeCanvas* canvas) { + if (background_) + background_->Paint(canvas, this); +} + +void View::PaintBorder(ChromeCanvas* canvas) { + if (border_) + border_->Paint(*this, canvas); +} + +void View::PaintFocusBorder(ChromeCanvas* canvas) { + if (HasFocus() && IsFocusable()) + canvas->DrawFocusRect(0, 0, GetWidth(), GetHeight()); +} + +void View::PaintChildren(ChromeCanvas* canvas) { + int i, c; + for (i = 0, c = GetChildViewCount(); i < c; ++i) { + View* child = GetChildViewAt(i); + if (!child) { + NOTREACHED() << "Should not have a NULL child View for index in bounds"; + continue; + } + child->ProcessPaint(canvas); + } +} + +void View::ProcessPaint(ChromeCanvas* canvas) { + if (!IsVisible()) { + return; + } + + // We're going to modify the canvas, save it's state first. + canvas->save(); + + // Paint this View and its children, setting the clip rect to the bounds + // of this View and translating the origin to the local bounds' top left + // point. + // + // Note that the X (or left) position we pass to ClipRectInt takes into + // consideration whether or not the view uses a right-to-left layout so that + // we paint our view in its mirrored position if need be. + if (canvas->ClipRectInt(MirroredX(), bounds_.top, bounds_.Width(), + bounds_.Height())) { + // Non-empty clip, translate the graphics such that 0,0 corresponds to + // where this view is located (related to its parent). + canvas->TranslateInt(MirroredX(), bounds_.top); + + // Save the state again, so that any changes don't effect PaintChildren. + canvas->save(); + + // If the View we are about to paint requested the canvas to be flipped, we + // should change the transform appropriately. + bool flip_canvas = FlipCanvasOnPaintForRTLUI(); + if (flip_canvas) { + canvas->TranslateInt(GetWidth(), 0); + canvas->ScaleInt(-1, 1); + canvas->save(); + } + + Paint(canvas); + + // We must undo the canvas mirroring once the View is done painting so that + // we don't pass the canvas with the mirrored transform to Views that + // didn't request the canvas to be flipped. + if (flip_canvas) { + canvas->restore(); + } + canvas->restore(); + PaintChildren(canvas); + } + + // Restore the canvas's original transform. + canvas->restore(); +} + +void View::PaintNow() { + if (!IsVisible()) { + return; + } + + View* view = GetParent(); + if (view) + view->PaintNow(); +} + +void View::PaintFloatingView(ChromeCanvas* canvas, View* view, + int x, int y, int w, int h) { + if (should_restore_focus_ && ShouldRestoreFloatingViewFocus()) { + // We are painting again a floating view, this is a good time to restore the + // focus to the last focused floating view if any. + should_restore_focus_ = false; + restore_focus_view_task_ = new RestoreFocusTask(this); + MessageLoop::current()->PostTask(FROM_HERE, restore_focus_view_task_); + } + View* saved_parent = view->GetParent(); + view->SetParent(this); + view->SetBounds(x, y, w, h); + view->Layout(); + view->ProcessPaint(canvas); + view->SetParent(saved_parent); +} + +void View::SetBackground(Background* b) { + if (background_ != b) + delete background_; + background_ = b; +} + +const Background* View::GetBackground() const { + return background_; +} + +void View::SetBorder(Border* b) { + if (border_ != b) + delete border_; + border_ = b; +} + +const Border* View::GetBorder() const { + return border_; +} + +gfx::Insets View::GetInsets() const { + const Border* border = GetBorder(); + gfx::Insets insets; + if (border) + border->GetInsets(&insets); + return insets; +} + +void View::SetContextMenuController(ContextMenuController* menu_controller) { + context_menu_controller_ = menu_controller; +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - tree +// +///////////////////////////////////////////////////////////////////////////// + +bool View::ProcessMousePressed(const MouseEvent& e, DragInfo* drag_info) { + const bool enabled = enabled_; + int drag_operations; + if (enabled && e.IsOnlyLeftMouseButton() && HitTest(e.GetLocation())) + drag_operations = GetDragOperations(e.GetX(), e.GetY()); + else + drag_operations = 0; + ContextMenuController* context_menu_controller = context_menu_controller_; + + const bool result = OnMousePressed(e); + // WARNING: we may have been deleted, don't use any View variables; + + if (!enabled) + return result; + + if (drag_operations != DragDropTypes::DRAG_NONE) { + drag_info->PossibleDrag(e.GetX(), e.GetY()); + return true; + } + return !!context_menu_controller || result; +} + +bool View::ProcessMouseDragged(const MouseEvent& e, DragInfo* drag_info) { + // Copy the field, that way if we're deleted after drag and drop no harm is + // done. + ContextMenuController* context_menu_controller = context_menu_controller_; + const bool possible_drag = drag_info->possible_drag; + if (possible_drag && ExceededDragThreshold(drag_info->start_x - e.GetX(), + drag_info->start_y - e.GetY())) { + DoDrag(e, drag_info->start_x, drag_info->start_y); + } else { + if (OnMouseDragged(e)) + return true; + // Fall through to return value based on context menu controller. + } + // WARNING: we may have been deleted. + return (context_menu_controller != NULL) || possible_drag; +} + +void View::ProcessMouseReleased(const MouseEvent& e, bool canceled) { + if (!canceled && context_menu_controller_ && e.IsOnlyRightMouseButton()) { + // Assume that if there is a context menu controller we won't be deleted + // from mouse released. + CPoint location(e.GetX(), e.GetY()); + ConvertPointToScreen(this, &location); + ContextMenuController* context_menu_controller = context_menu_controller_; + OnMouseReleased(e, canceled); + context_menu_controller_->ShowContextMenu(this, location.x, location.y, + true); + } else { + OnMouseReleased(e, canceled); + } + // WARNING: we may have been deleted. +} + +void View::DoDrag(const MouseEvent& e, int press_x, int press_y) { + scoped_refptr<OSExchangeData> data = new OSExchangeData; + WriteDragData(press_x, press_y, data.get()); + + // Message the RootView to do the drag and drop. That way if we're removed + // the RootView can detect it and avoid callins us back. + RootView* root_view = GetRootView(); + root_view->StartDragForViewFromMouseEvent( + this, data, GetDragOperations(press_x, press_y)); +} + +void View::AddChildView(View* v) { + AddChildView(static_cast<int>(child_views_.size()), v, false); +} + +void View::AddChildView(int index, View* v) { + AddChildView(index, v, false); +} + +void View::AddChildView(int index, View* v, bool floating_view) { + // Remove the view from its current parent if any. + if (v->GetParent()) + v->GetParent()->RemoveChildView(v); + + if (!floating_view) { + // Sets the prev/next focus views. + InitFocusSiblings(v, index); + } + + // Let's insert the view. + child_views_.insert(child_views_.begin() + index, v); + v->SetParent(this); + + for (View* p = this; p; p = p->GetParent()) { + p->ViewHierarchyChangedImpl(false, true, this, v); + } + v->PropagateAddNotifications(this, v); + UpdateTooltip(); + RootView* root = GetRootView(); + if (root) + RegisterChildrenForVisibleBoundsNotification(root, v); + + if (layout_manager_.get()) + layout_manager_->ViewAdded(this, v); +} + +View* View::GetChildViewAt(int index) const { + return index < GetChildViewCount() ? child_views_[index] : NULL; +} + +int View::GetChildViewCount() const { + return static_cast<int>(child_views_.size()); +} + +void View::RemoveChildView(View* a_view) { + DoRemoveChildView(a_view, true, true, false); +} + +void View::RemoveAllChildViews(bool delete_views) { + ViewList::iterator iter; + while ((iter = child_views_.begin()) != child_views_.end()) { + DoRemoveChildView(*iter, false, false, delete_views); + } + UpdateTooltip(); +} + +void View::DoRemoveChildView(View* a_view, + bool update_focus_cycle, + bool update_tool_tip, + bool delete_removed_view) { +#ifndef NDEBUG + DCHECK(!IsProcessingPaint()) << "Should not be removing a child view " << + "during a paint, this will seriously " << + "mess things up!"; +#endif + DCHECK(a_view); + const ViewList::iterator i = find(child_views_.begin(), + child_views_.end(), + a_view); + if (i != child_views_.end()) { + if (update_focus_cycle && !a_view->IsFloatingView()) { + // Let's remove the view from the focus traversal. + View* next_focusable = a_view->next_focusable_view_; + View* prev_focusable = a_view->previous_focusable_view_; + if (prev_focusable) + prev_focusable->next_focusable_view_ = next_focusable; + if (next_focusable) + next_focusable->previous_focusable_view_ = prev_focusable; + } + + RootView* root = GetRootView(); + if (root) + UnregisterChildrenForVisibleBoundsNotification(root, a_view); + a_view->PropagateRemoveNotifications(this); + a_view->SetParent(NULL); + + if (delete_removed_view && a_view->IsParentOwned()) + delete a_view; + + child_views_.erase(i); + } + + if (update_tool_tip) + UpdateTooltip(); + + if (layout_manager_.get()) + layout_manager_->ViewRemoved(this, a_view); +} + +void View::PropagateRemoveNotifications(View* parent) { + int i, c; + for (i = 0, c = GetChildViewCount(); i < c; ++i) { + GetChildViewAt(i)->PropagateRemoveNotifications(parent); + } + + View *t; + for (t = this; t; t = t->GetParent()) { + t->ViewHierarchyChangedImpl(true, false, parent, this); + } +} + +void View::PropagateAddNotifications(View* parent, View* child) { + int i, c; + for (i = 0, c = GetChildViewCount(); i < c; ++i) { + GetChildViewAt(i)->PropagateAddNotifications(parent, child); + } + ViewHierarchyChangedImpl(true, true, parent, child); +} + +#ifndef NDEBUG +bool View::IsProcessingPaint() const { + return GetParent() && GetParent()->IsProcessingPaint(); +} +#endif + +void View::ViewHierarchyChanged(bool is_add, View *parent, View *child) { +} + +void View::ViewHierarchyChangedImpl(bool register_accelerators, + bool is_add, View *parent, View *child) { + if (register_accelerators) { + if (is_add) { + // If you get this registration, you are part of a subtree that has been + // added to the view hierarchy. + RegisterAccelerators(); + } else { + if (child == this) + UnregisterAccelerators(); + } + } + + ViewHierarchyChanged(is_add, parent, child); +} + +void View::PropagateVisibilityNotifications(View* start, bool is_visible) { + int i, c; + for (i = 0, c = GetChildViewCount(); i < c; ++i) { + GetChildViewAt(i)->PropagateVisibilityNotifications(start, is_visible); + } + VisibilityChanged(start, is_visible); +} + +void View::VisibilityChanged(View* starting_from, bool is_visible) { +} + +View* View::GetViewForPoint(const CPoint& point) { + return GetViewForPoint(point, true); +} + +void View::SetNotifyWhenVisibleBoundsInRootChanges(bool value) { + if (notify_when_visible_bounds_in_root_changes_ == value) + return; + notify_when_visible_bounds_in_root_changes_ = value; + RootView* root = GetRootView(); + if (root) { + if (value) + root->RegisterViewForVisibleBoundsNotification(this); + else + root->UnregisterViewForVisibleBoundsNotification(this); + } +} + +bool View::GetNotifyWhenVisibleBoundsInRootChanges() { + return notify_when_visible_bounds_in_root_changes_; +} + +View* View::GetViewForPoint(const CPoint& point, bool can_create_floating) { + // Walk the child Views recursively looking for the View that most + // tightly encloses the specified point. + for (int i = GetChildViewCount() - 1 ; i >= 0 ; --i) { + View* child = GetChildViewAt(i); + if (!child->IsVisible()) { + continue; + } + CRect bounds; + child->GetBounds(&bounds, APPLY_MIRRORING_TRANSFORMATION); + if (bounds.PtInRect(point)) { + CPoint cl(point); + cl.Offset(-bounds.left, -bounds.top); + return child->GetViewForPoint(cl, true); + } + } + + // We haven't found a view for the point. Try to create floating views + // and try again if one was created. + // can_create_floating makes sure we don't try forever even if + // GetFloatingViewIDForPoint lies or if RetrieveFloatingViewForID creates a + // view which doesn't contain the provided point + int id; + if (can_create_floating && GetFloatingViewIDForPoint(point.x, point.y, &id)) { + RetrieveFloatingViewForID(id); // This creates the floating view. + return GetViewForPoint(point, false); + } + return this; +} + +ViewContainer* View::GetViewContainer() const { + // The root view holds a reference to this view hierarchy's container. + return parent_ ? parent_->GetViewContainer() : NULL; +} + +// Get the containing RootView +RootView* View::GetRootView() { + ViewContainer* vc = GetViewContainer(); + if (vc) { + return vc->GetRootView(); + } else { + return NULL; + } +} + +View* View::GetViewByID(int id) const { + if (id == id_) + return const_cast<View*>(this); + + int view_count = GetChildViewCount(); + for (int i = 0; i < view_count; ++i) { + View* child = GetChildViewAt(i); + View* view = child->GetViewByID(id); + if (view) + return view; + } + return NULL; +} + +void View::GetViewsWithGroup(int group_id, std::vector<View*>* out) { + if (group_ == group_id) + out->push_back(this); + + int view_count = GetChildViewCount(); + for (int i = 0; i < view_count; ++i) + GetChildViewAt(i)->GetViewsWithGroup(group_id, out); +} + +View* View::GetSelectedViewForGroup(int group_id) { + std::vector<View*> views; + GetRootView()->GetViewsWithGroup(group_id, &views); + if (views.size() > 0) + return views[0]; + else + return NULL; +} + +void View::SetID(int id) { + id_ = id; +} + +int View::GetID() const { + return id_; +} + +void View::SetGroup(int gid) { + group_ = gid; +} + +int View::GetGroup() const { + return group_; +} + +void View::SetParent(View* parent) { + if (parent != parent_) { + parent_ = parent; + } +} + +bool View::IsParentOf(View* v) const { + DCHECK(v); + View* parent = v->GetParent(); + while (parent) { + if (this == parent) + return true; + parent = parent->GetParent(); + } + return false; +} + +int View::GetChildIndex(View* v) const { + for (int i = 0; i < GetChildViewCount(); i++) { + if (v == GetChildViewAt(i)) + return i; + } + return -1; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// View - focus +// +/////////////////////////////////////////////////////////////////////////////// + +View* View::GetNextFocusableView() { + return next_focusable_view_; +} + +View* View::GetPreviousFocusableView() { + return previous_focusable_view_; +} + +void View::SetNextFocusableView(View* view) { + view->previous_focusable_view_ = this; + next_focusable_view_ = view; +} + +void View::InitFocusSiblings(View* v, int index) { + int child_count = static_cast<int>(child_views_.size()); + + if (child_count == 0) { + v->next_focusable_view_ = NULL; + v->previous_focusable_view_ = NULL; + } else { + if (index == child_count) { + // We are inserting at the end, but the end of the child list may not be + // the last focusable element. Let's try to find an element with no next + // focusable element to link to. + View* last_focusable_view = NULL; + for (std::vector<View*>::iterator iter = child_views_.begin(); + iter != child_views_.end(); ++iter) { + if (!(*iter)->next_focusable_view_) { + last_focusable_view = *iter; + break; + } + } + if (last_focusable_view == NULL) { + // Hum... there is a cycle in the focus list. Let's just insert ourself + // after the last child. + View* prev = child_views_[index - 1]; + v->previous_focusable_view_ = prev; + v->next_focusable_view_ = prev->next_focusable_view_; + prev->next_focusable_view_->previous_focusable_view_ = v; + prev->next_focusable_view_ = v; + } else { + last_focusable_view->next_focusable_view_ = v; + v->next_focusable_view_ = NULL; + v->previous_focusable_view_ = last_focusable_view; + } + } else { + View* prev = child_views_[index]->GetPreviousFocusableView(); + v->previous_focusable_view_ = prev; + v->next_focusable_view_ = child_views_[index]; + if (prev) + prev->next_focusable_view_ = v; + child_views_[index]->previous_focusable_view_ = v; + } + } +} + +#ifndef NDEBUG +void View::PrintViewHierarchy() { + PrintViewHierarchyImp(0); +} + +void View::PrintViewHierarchyImp(int indent) { + std::wostringstream buf; + int ind = indent; + while (ind-- > 0) + buf << L' '; + buf << UTF8ToWide(GetClassName()); + buf << L' '; + buf << GetID(); + buf << L' '; + buf << bounds_.left << L"," << bounds_.top << L","; + buf << bounds_.right << L"," << bounds_.bottom; + buf << L' '; + buf << this; + + LOG(INFO) << buf.str(); + std::cout << buf.str() << std::endl; + + for (int i = 0; i < GetChildViewCount(); ++i) { + GetChildViewAt(i)->PrintViewHierarchyImp(indent + 2); + } +} + + +void View::PrintFocusHierarchy() { + PrintFocusHierarchyImp(0); +} + +void View::PrintFocusHierarchyImp(int indent) { + std::wostringstream buf; + int ind = indent; + while (ind-- > 0) + buf << L' '; + buf << UTF8ToWide(GetClassName()); + buf << L' '; + buf << GetID(); + buf << L' '; + buf << GetClassName().c_str(); + buf << L' '; + buf << this; + + LOG(INFO) << buf.str(); + std::cout << buf.str() << std::endl; + + if (GetChildViewCount() > 0) + GetChildViewAt(0)->PrintFocusHierarchyImp(indent + 2); + + View* v = GetNextFocusableView(); + if (v) + v->PrintFocusHierarchyImp(indent); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// View - accelerators +// +//////////////////////////////////////////////////////////////////////////////// + +void View::AddAccelerator(const Accelerator& accelerator) { + if (!accelerators_.get()) + accelerators_.reset(new std::vector<Accelerator>()); + accelerators_->push_back(accelerator); + RegisterAccelerators(); +} + +void View::ResetAccelerators() { + if (accelerators_.get()) { + UnregisterAccelerators(); + accelerators_->clear(); + accelerators_.reset(); + } +} + +void View::RegisterAccelerators() { + if (!accelerators_.get()) + return; + + RootView* root_view = GetRootView(); + if (!root_view) { + // We are not yet part of a view hierarchy, we'll register ourselves once + // added to one. + return; + } + FocusManager* focus_manager = GetFocusManager(); + if (!focus_manager) { + // Some crash reports seem to show that we may get cases where we have no + // focus manager (see bug #1291225). This should never be the case, just + // making sure we don't crash. + NOTREACHED(); + return; + } + for (std::vector<Accelerator>::const_iterator iter = accelerators_->begin(); + iter != accelerators_->end(); ++iter) { + focus_manager->RegisterAccelerator(*iter, this); + } +} + +void View::UnregisterAccelerators() { + if (!accelerators_.get()) + return; + + RootView* root_view = GetRootView(); + if (root_view) { + FocusManager* focus_manager = GetFocusManager(); + if (focus_manager) { + // We may not have a FocusManager if the window containing us is being + // closed, in which case the FocusManager is being deleted so there is + // nothing to unregister. + focus_manager->UnregisterAccelerators(this); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - accessibility +// +///////////////////////////////////////////////////////////////////////////// + +AccessibleWrapper* View::GetAccessibleWrapper() { + if (accessibility_.get() == NULL) { + accessibility_.reset(new AccessibleWrapper(this)); + } + return accessibility_.get(); +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - floating views +// +///////////////////////////////////////////////////////////////////////////// + +bool View::IsFloatingView() { + if (!parent_) + return false; + + return parent_->floating_views_ids_.find(this) != + parent_->floating_views_ids_.end(); +} + +// default implementation does nothing +bool View::GetFloatingViewIDForPoint(int x, int y, int* id) { + return false; +} + +int View::GetFloatingViewCount() const { + return static_cast<int>(floating_views_.size()); +} + +View* View::RetrieveFloatingViewParent() { + View* v = this; + while (v) { + if (v->IsFloatingView()) + return v; + v = v->GetParent(); + } + return NULL; +} + +bool View::EnumerateFloatingViews(FloatingViewPosition position, + int starting_id, int* id) { + return false; +} + +int View::GetDragOperations(int press_x, int press_y) { + if (!drag_controller_) + return DragDropTypes::DRAG_NONE; + return drag_controller_->GetDragOperations(this, press_x, press_y); +} + +void View::WriteDragData(int press_x, int press_y, OSExchangeData* data) { + DCHECK(drag_controller_); + drag_controller_->WriteDragData(this, press_x, press_y, data); +} + +void View::OnDragDone() { +} + +bool View::InDrag() { + RootView* root_view = GetRootView(); + return root_view ? (root_view->GetDragView() == this) : false; +} + +View* View::ValidateFloatingViewForID(int id) { + return NULL; +} + +bool View::ShouldRestoreFloatingViewFocus() { + return true; +} + +void View::AttachFloatingView(View* v, int id) { + floating_views_.push_back(v); + floating_views_ids_[v] = id; + AddChildView(static_cast<int>(child_views_.size()), v, true); +} + +bool View::HasFloatingViewForPoint(int x, int y) { + int i, c; + View* v; + gfx::Rect r; + + for (i = 0, c = static_cast<int>(floating_views_.size()); i < c; ++i) { + v = floating_views_[i]; + r.SetRect(v->GetX(APPLY_MIRRORING_TRANSFORMATION), v->GetY(), + v->GetWidth(), v->GetHeight()); + if (r.Contains(x, y)) + return true; + } + return false; +} + +void View::DetachAllFloatingViews() { + RootView* root_view = GetRootView(); + View* focused_view = NULL; + FocusManager* focus_manager = NULL; + if (root_view) { + // We may be called when we are not attached to a root view in which case + // there is nothing to do for focus. + focus_manager = GetFocusManager(); + if (focus_manager) { + // We may not have a focus manager (if we are detached from a top window). + focused_view = focus_manager->GetFocusedView(); + } + } + + int c = static_cast<int>(floating_views_.size()); + while (--c >= 0) { + // If the focused view is a floating view or a floating view's children, + // use the focus manager to store it. + int tmp_id; + if (focused_view && + ((focused_view == floating_views_[c]) || + floating_views_[c]->IsParentOf(focused_view))) { + // We call EnumerateFloatingView to make sure the floating view is still + // valid: the model may have changed and could not know anything about + // that floating view anymore. + if (EnumerateFloatingViews(CURRENT, + floating_views_[c]->GetFloatingViewID(), + &tmp_id)) { + focus_manager->StoreFocusedView(); + should_restore_focus_ = true; + } + focused_view = NULL; + } + + RemoveChildView(floating_views_[c]); + delete floating_views_[c]; + } + floating_views_.clear(); + floating_views_ids_.clear(); +} + +int View::GetFloatingViewID() { + DCHECK(IsFloatingView()); + std::map<View*, int>::iterator iter = parent_->floating_views_ids_.find(this); + DCHECK(iter != parent_->floating_views_ids_.end()); + return iter->second; +} + +View* View::RetrieveFloatingViewForID(int id) { + for (ViewList::const_iterator iter = floating_views_.begin(); + iter != floating_views_.end(); ++iter) { + if ((*iter)->GetFloatingViewID() == id) + return *iter; + } + return ValidateFloatingViewForID(id); +} + +void View::RestoreFloatingViewFocus() { + // Clear the reference to the task as if we have been triggered by it, it will + // soon be invalid. + restore_focus_view_task_ = NULL; + should_restore_focus_ = false; + + GetFocusManager()->RestoreFocusedView(); +} + +// static +bool View::EnumerateFloatingViewsForInterval(int low_bound, int high_bound, + bool ascending_order, + FloatingViewPosition position, + int starting_id, + int* id) { + DCHECK(low_bound <= high_bound); + if (low_bound >= high_bound) + return false; + + switch (position) { + case CURRENT: + if ((starting_id >= low_bound) && (starting_id < high_bound)) { + *id = starting_id; + return true; + } + return false; + case FIRST: + *id = ascending_order ? low_bound : high_bound - 1; + return true; + case LAST: + *id = ascending_order ? high_bound - 1 : low_bound; + return true; + case NEXT: + case PREVIOUS: + if (((position == NEXT) && ascending_order) || + ((position == PREVIOUS) && !ascending_order)) { + starting_id++; + if (starting_id < high_bound) { + *id = starting_id; + return true; + } + return false; + } + DCHECK(((position == NEXT) && !ascending_order) || + ((position == PREVIOUS) && ascending_order)); + starting_id--; + if (starting_id >= low_bound) { + *id = starting_id; + return true; + } + return false; + default: + NOTREACHED(); + } + return false; +} + +// static +void View::ConvertPointToView(View* src, + View* dst, + gfx::Point* point) { + ConvertPointToView(src, dst, point, true); +} + +// static +void View::ConvertPointToView(View* src, + View* dst, + CPoint* point) { + gfx::Point tmp_point(point->x, point->y); + ConvertPointToView(src, dst, &tmp_point, true); + point->x = tmp_point.x(); + point->y = tmp_point.y(); +} + +// static +void View::ConvertPointToView(View* src, + View* dst, + gfx::Point* point, + bool try_other_direction) { + // src can be NULL + DCHECK(dst); + DCHECK(point); + + View* v; + gfx::Point offset; + + for (v = dst; v && v != src; v = v->GetParent()) { + offset.SetPoint(offset.x() + v->GetX(APPLY_MIRRORING_TRANSFORMATION), + offset.y() + v->GetY()); + } + + // The source was not found. The caller wants a conversion + // from a view to a transitive parent. + if (src && v == NULL && try_other_direction) { + gfx::Point p; + // note: try_other_direction is force to FALSE so we don't + // end up in an infinite recursion should both src and dst + // are not parented. + ConvertPointToView(dst, src, &p, false); + // since the src and dst are inverted, p should also be negated + point->SetPoint(point->x() - p.x(), point->y() - p.y()); + } else { + point->SetPoint(point->x() - offset.x(), point->y() - offset.y()); + + // If src is NULL, sp is in the screen coordinate system + if (src == NULL) { + ViewContainer* vc = dst->GetViewContainer(); + if (vc) { + CRect b; + vc->GetBounds(&b, false); + point->SetPoint(point->x() - b.left, point->y() - b.top); + } + } + } +} + +// static +void View::ConvertPointToViewContainer(View* src, CPoint* p) { + DCHECK(src); + DCHECK(p); + View *v; + CPoint offset(0, 0); + + for (v = src; v; v = v->GetParent()) { + offset.x += v->GetX(APPLY_MIRRORING_TRANSFORMATION); + offset.y += v->GetY(); + } + p->x += offset.x; + p->y += offset.y; +} + +// static +void View::ConvertPointFromViewContainer(View *source, CPoint *p) { + CPoint t(0, 0); + ConvertPointToViewContainer(source, &t); + p->x -= t.x; + p->y -= t.y; +} + +// static +void View::ConvertPointToScreen(View* src, CPoint* p) { + DCHECK(src); + DCHECK(p); + + // If the view is not connected to a tree, do nothing + if (src->GetViewContainer() == NULL) { + return; + } + + ConvertPointToViewContainer(src, p); + ViewContainer* vc = src->GetViewContainer(); + if (vc) { + CRect r; + vc->GetBounds(&r, false); + p->x += r.left; + p->y += r.top; + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - event handlers +// +///////////////////////////////////////////////////////////////////////////// + +bool View::OnMousePressed(const MouseEvent& e) { + return false; +} + +bool View::OnMouseDragged(const MouseEvent& e) { + return false; +} + +void View::OnMouseReleased(const MouseEvent& e, bool canceled) { +} + +void View::OnMouseMoved(const MouseEvent& e) { +} + +void View::OnMouseEntered(const MouseEvent& e) { +} + +void View::OnMouseExited(const MouseEvent& e) { +} + +void View::SetMouseHandler(View *new_mouse_handler) { + // It is valid for new_mouse_handler to be NULL + if (parent_) { + parent_->SetMouseHandler(new_mouse_handler); + } +} + +void View::SetVisible(bool flag) { + if (flag != is_visible_) { + // If the tab is currently visible, schedule paint to + // refresh parent + if (IsVisible()) { + SchedulePaint(); + } + + is_visible_ = flag; + + // This notifies all subviews recursively. + PropagateVisibilityNotifications(this, flag); + + // If we are newly visible, schedule paint. + if (IsVisible()) { + SchedulePaint(); + } + } +} + +bool View::IsVisibleInRootView() const { + View* parent = GetParent(); + if (IsVisible() && parent) + return parent->IsVisibleInRootView(); + else + return false; +} + +bool View::HitTest(const CPoint &l) const { + if (l.x >= 0 && l.x < static_cast<int>(GetWidth()) && + l.y >= 0 && l.y < static_cast<int>(GetHeight())) { + return true; + } else { + return false; + } +} + +HCURSOR View::GetCursorForPoint(Event::EventType event_type, int x, int y) { + return NULL; +} + +///////////////////////////////////////////////////////////////////////////// +// +// View - keyboard and focus +// +///////////////////////////////////////////////////////////////////////////// + +void View::RequestFocus() { + RootView* rv = GetRootView(); + if (rv) { + rv->FocusView(this); + } +} + +void View::WillGainFocus() { +} + +void View::DidGainFocus() { +} + +void View::WillLoseFocus() { +} + +bool View::OnKeyPressed(const KeyEvent& e) { + return false; +} + +bool View::OnKeyReleased(const KeyEvent& e) { + return false; +} + +bool View::OnMouseWheel(const MouseWheelEvent& e) { + return false; +} + +void View::SetDragController(DragController* drag_controller) { + drag_controller_ = drag_controller; +} + +DragController* View::GetDragController() { + return drag_controller_; +} + +bool View::CanDrop(const OSExchangeData& data) { + return false; +} + +void View::OnDragEntered(const DropTargetEvent& event) { +} + +int View::OnDragUpdated(const DropTargetEvent& event) { + return DragDropTypes::DRAG_NONE; +} + +void View::OnDragExited() { +} + +int View::OnPerformDrop(const DropTargetEvent& event) { + return DragDropTypes::DRAG_NONE; +} + +static int GetHorizontalDragThreshold() { + static int threshold = -1; + if (threshold == -1) + threshold = GetSystemMetrics(SM_CXDRAG) / 2; + return threshold; +} + +static int GetVerticalDragThreshold() { + static int threshold = -1; + if (threshold == -1) + threshold = GetSystemMetrics(SM_CYDRAG) / 2; + return threshold; +} + +// static +bool View::ExceededDragThreshold(int delta_x, int delta_y) { + return (abs(delta_x) > GetHorizontalDragThreshold() || + abs(delta_y) > GetVerticalDragThreshold()); +} + +void View::Focus() { + // Set the native focus to the root view window so it receives the keyboard + // messages. + FocusManager* focus_manager = GetFocusManager(); + if (focus_manager) + focus_manager->FocusHWND(GetRootView()->GetViewContainer()->GetHWND()); +} + +bool View::CanProcessTabKeyEvents() { + return false; +} + +// Tooltips ----------------------------------------------------------------- +bool View::GetTooltipText(int x, int y, std::wstring* tooltip) { + return false; +} + +bool View::GetTooltipTextOrigin(int x, int y, CPoint* loc) { + return false; +} + +void View::TooltipTextChanged() { + ViewContainer* view_container = GetViewContainer(); + if (view_container != NULL && view_container->GetTooltipManager()) + view_container->GetTooltipManager()->TooltipTextChanged(this); +} + +void View::UpdateTooltip() { + ViewContainer* view_container = GetViewContainer(); + if (view_container != NULL && view_container->GetTooltipManager()) + view_container->GetTooltipManager()->UpdateTooltip(); +} + +void View::SetParentOwned(bool f) { + is_parent_owned_ = f; +} + +bool View::IsParentOwned() const { + return is_parent_owned_; +} + +std::string View::GetClassName() const { + return kViewClassName; +} + +gfx::Rect View::GetVisibleBounds() { + gfx::Rect vis_bounds(0, 0, GetWidth(), GetHeight()); + gfx::Rect ancestor_bounds; + View* view = this; + int root_x = 0; + int root_y = 0; + bool has_view_container = false; + while (view != NULL && !vis_bounds.IsEmpty()) { + root_x += view->GetX(APPLY_MIRRORING_TRANSFORMATION); + root_y += view->GetY(); + vis_bounds.Offset(view->GetX(APPLY_MIRRORING_TRANSFORMATION), view->GetY()); + View* ancestor = view->GetParent(); + if (ancestor != NULL) { + ancestor_bounds.SetRect(0, 0, ancestor->GetWidth(), + ancestor->GetHeight()); + vis_bounds = vis_bounds.Intersect(ancestor_bounds); + } else if (!view->GetViewContainer()) { + // If the view has no ViewContainer, we're not visible. Return an empty + // rect. + return gfx::Rect(); + } + view = ancestor; + } + if (vis_bounds.IsEmpty()) + return vis_bounds; + // Convert back to this views coordinate system. + vis_bounds.Offset(-root_x, -root_y); + return vis_bounds; +} + +int View::GetPageScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive) { + return 0; +} + +int View::GetLineScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive) { + return 0; +} + +// static +void View::RegisterChildrenForVisibleBoundsNotification( + RootView* root, View* view) { + DCHECK(root && view); + if (view->GetNotifyWhenVisibleBoundsInRootChanges()) + root->RegisterViewForVisibleBoundsNotification(view); + for (int i = 0; i < view->GetChildViewCount(); ++i) + RegisterChildrenForVisibleBoundsNotification(root, view->GetChildViewAt(i)); +} + +// static +void View::UnregisterChildrenForVisibleBoundsNotification( + RootView* root, View* view) { + DCHECK(root && view); + if (view->GetNotifyWhenVisibleBoundsInRootChanges()) + root->UnregisterViewForVisibleBoundsNotification(view); + for (int i = 0; i < view->GetChildViewCount(); ++i) + UnregisterChildrenForVisibleBoundsNotification(root, + view->GetChildViewAt(i)); +} + +void View::AddDescendantToNotify(View* view) { + DCHECK(view); + if (!descendants_to_notify_.get()) + descendants_to_notify_.reset(new ViewList()); + descendants_to_notify_->push_back(view); +} + +void View::RemoveDescendantToNotify(View* view) { + DCHECK(view && descendants_to_notify_.get()); + ViewList::iterator i = find(descendants_to_notify_->begin(), + descendants_to_notify_->end(), + view); + DCHECK(i != descendants_to_notify_->end()); + descendants_to_notify_->erase(i); + if (descendants_to_notify_->empty()) + descendants_to_notify_.reset(); +} + +// static +bool View::GetViewPath(View* start, View* end, std::vector<int>* path) { + while (end && (end != start)) { + View* parent = end->GetParent(); + if (!parent) + return false; + path->insert(path->begin(), parent->GetChildIndex(end)); + end = parent; + } + return end == start; +} + +// static +View* View::GetViewForPath(View* start, const std::vector<int>& path) { + View* v = start; + for (std::vector<int>::const_iterator iter = path.begin(); + iter != path.end(); ++iter) { + int index = *iter; + if (index >= v->GetChildViewCount()) + return NULL; + v = v->GetChildViewAt(index); + } + return v; +} + +// DropInfo -------------------------------------------------------------------- + +void View::DragInfo::Reset() { + possible_drag = false; + start_x = start_y = 0; +} + +void View::DragInfo::PossibleDrag(int x, int y) { + possible_drag = true; + start_x = x; + start_y = y; +} + +} // namespace diff --git a/chrome/views/view.h b/chrome/views/view.h new file mode 100644 index 0000000..066550a --- /dev/null +++ b/chrome/views/view.h @@ -0,0 +1,1323 @@ +// 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. + +#ifndef CHROME_VIEWS_VIEW_H__ +#define CHROME_VIEWS_VIEW_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/gfx/rect.h" +#include "base/scoped_ptr.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/gfx/insets.h" +#include "chrome/views/accessibility/accessible_wrapper.h" +#include "chrome/views/accelerator.h" +#include "chrome/views/event.h" +#include "chrome/views/view_container.h" + +class ChromeCanvas; +class OSExchangeData; +class SkBitmap; + +namespace ChromeViews { + +class Background; +class Border; +class FocusManager; +class FocusTraversable; +class LayoutManager; +class RestoreFocusTask; +class RootView; +class ScrollView; +class ViewContainer; + +// ContextMenuController is responsible for showing the context menu for a +// View. To use a ContextMenuController invoke SetContextMenuController on a +// View. When the appropriate user gesture occurs ShowContextMenu is invoked +// on the ContextMenuController. +// +// Setting a ContextMenuController on a view makes the view process mouse +// events. +// +// It is up to subclasses that do their own mouse processing to invoke +// the appropriate ContextMenuController method, typically by invoking super's +// implementation for mouse processing. +// +class ContextMenuController { + public: + // Invoked to show the context menu for the source view. If is_mouse_gesture + // is true, the x/y coordinate are the location of the mouse. If + // is_mouse_gesture is false, this method was not invoked by a mouse gesture + // and x/y is the recommended location to show the menu at. + // + // x/y is in screen coordinates. + virtual void ShowContextMenu(View* source, + int x, + int y, + bool is_mouse_gesture) = 0; +}; + +// DragController is responsible for writing drag data for a view, as well as +// supplying the supported drag operations. Use DragController if you don't +// want to subclass. + +class DragController { + public: + // Writes the data for the drag. + virtual void WriteDragData(View* sender, + int press_x, + int press_y, + OSExchangeData* data) = 0; + + // Returns the supported drag operations (see DragDropTypes for possible + // values). A drag is only started if this returns a non-zero value. + virtual int GetDragOperations(View* sender, int x, int y) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// View class +// +// A View is a rectangle within the ChromeViews View hierarchy. It is the +// base class for all Views. +// +// A View is a container of other Views (there is no such thing as a Leaf +// View - makes code simpler, reduces type conversion headaches, design +// mistakes etc) +// +// The View contains basic properties for sizing (bounds), layout (flex, +// orientation, etc), painting of children and event dispatch. +// +// The View also uses a simple Box Layout Manager similar to XUL's +// SprocketLayout system. Alternative Layout Managers implementing the +// LayoutManager interface can be used to lay out children if required. +// +// It is up to the subclass to implement Painting and storage of subclass - +// specific properties and functionality. +// +///////////////////////////////////////////////////////////////////////////// +class View : public AcceleratorTarget { + public: + + // Used in EnumerateFloatingViews() to specify which floating view to + // retrieve. + enum FloatingViewPosition { + FIRST = 0, + NEXT, + PREVIOUS, + LAST, + CURRENT + }; + + // Used in the versions of GetBounds() and GetX() that take a transformation + // parameter in order to determine whether or not to take into account the + // mirroring setting of the View when returning bounds positions. + enum PositionMirroringSettings { + IGNORE_MIRRORING_TRANSFORMATION = 0, + APPLY_MIRRORING_TRANSFORMATION + }; + + // The view class name. + static char kViewClassName[]; + + View(); + virtual ~View(); + + // Sizing functions + + // Get the bounds of the View, relative to the parent. Essentially, this + // function returns the bounds_ rectangle. + // + // This is the function subclasses should use whenever they need to obtain + // the bounds of one of their child views (for example, when implementing + // View::Layout()). + void GetBounds(CRect *out) const { + GetBounds(out, IGNORE_MIRRORING_TRANSFORMATION); + }; + + // Return the bounds of the View, relative to the parent. If + // |settings| is IGNORE_MIRRORING_TRANSFORMATION, the function returns the + // bounds_ rectangle. If |settings| is APPLY_MIRRORING_SETTINGS AND the + // parent View is using a right-to-left UI layout, then the function returns + // a shifted version of the bounds_ rectangle that represents the mirrored + // View bounds. + // + // NOTE: in the vast majority of the cases, the mirroring implementation is + // transparent to the View subclasses and therefore you should use the + // version of GetBounds() which does not take a transformation settings + // parameter. + void GetBounds(CRect *out, PositionMirroringSettings settings) const; + + // Set the bounds in the parent's coordinate system. + void SetBounds(const CRect& bounds); + void SetBounds(int x, int y, int width, int height); + void SetX(int x) { SetBounds(x, GetY(), GetWidth(), GetHeight()); } + void SetY(int y) { SetBounds(GetX(), y, GetWidth(), GetHeight()); } + + // Returns the left coordinate of the View, relative to the parent View, + // which is essentially the value of bounds_.left. + // + // This is the function subclasses should use whenever they need to obtain + // the left position of one of their child views (for example, when + // implementing View::Layout()). + inline int GetX() const { + return GetX(IGNORE_MIRRORING_TRANSFORMATION); + }; + + // Return the left coordinate of the View, relative to the parent. If + // |settings| is IGNORE_MIRRORING_SETTINGS, the function returns the value of + // bounds_.left. If |settings| is APPLY_MIRRORING_SETTINGS AND the parent + // View is using a right-to-left UI layout, then the function returns the + // mirrored value of bounds_.left. + // + // NOTE: in the vast majority of the cases, the mirroring implementation is + // transparent to the View subclasses and therefore you should use the + // paremeterless version of GetX() when you need to get the X + // coordinate of a child View. + int GetX(PositionMirroringSettings settings) const; + + inline int GetY() const { + return bounds_.top; + }; + inline int GetWidth() const { + return bounds_.Width(); + }; + inline int GetHeight() const { + return bounds_.Height(); + }; + + // Return this control local bounds. If include_border is true, local bounds + // is the rectangle {0, 0, GetWidth(), GetHeight()}, otherwise, it does not + // include the area where the border (if any) is painted. + void GetLocalBounds(CRect* out, bool include_border) const; + + // Get the size of the View + void GetSize(CSize* out) const; + + // Get the position of the View, relative to the parent. + // + // Note that if the parent uses right-to-left UI layout, then the mirrored + // position of this View is returned. Use GetX()/GetY() if you want to ignore + // mirroring. + void GetPosition(CPoint* out) const; + + // Get the size the View would like to be, if enough space were available. + virtual void GetPreferredSize(CSize* out); + + // Convenience method that sizes this view to its preferred size. + void SizeToPreferredSize(); + + // Gets the minimum size of the view. View's implementation invokes + // GetPreferredSize. + virtual void GetMinimumSize(CSize* out); + + // Return the height necessary to display this view with the provided width. + // View's implementation returns the value from getPreferredSize.cy. + // Override if your View's preferred height depends upon the width (such + // as with Labels). + virtual int GetHeightForWidth(int w); + + // This method is invoked when this object size or position changes. + // The default implementation does nothing. + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Set whether the receiving view is visible. Painting is scheduled as needed + virtual void SetVisible(bool flag); + + // Return whether a view is visible + virtual bool IsVisible() const { return is_visible_; } + + // Return whether a view and its ancestors are visible. Returns true if the + // path from this view to the root view is visible. + virtual bool IsVisibleInRootView() const; + + // Set whether this view is enabled. A disabled view does not receive keyboard + // or mouse inputs. If flag differs from the current value, SchedulePaint is + // invoked. + virtual void SetEnabled(bool flag); + + // Returns whether the view is enabled. + virtual bool IsEnabled() const; + + // Set whether this view is hottracked. A disabled view cannot be hottracked. + // If flag differs from the current value, SchedulePaint is invoked. + virtual void SetHotTracked(bool flag); + + // Returns whether the view is hot-tracked. + virtual bool IsHotTracked() const { return false; } + + // Returns whether the view is pushed. + virtual bool IsPushed() const { return false; } + + // Scrolls the specified region, in this View's coordinate system, to be + // visible. View's implementation passes the call onto the parent View (after + // adjusting the coordinates). It is up to views that only show a portion of + // the child view, such as Viewport, to override appropriately. + virtual void ScrollRectToVisible(int x, int y, int width, int height); + + // Layout functions + + // Lay out the child Views (set their bounds based on sizing heuristics + // specific to the current Layout Manager) + virtual void Layout(); + + // Gets/Sets the Layout Manager used by this view to size and place its + // children. + // The LayoutManager is owned by the View and is deleted when the view is + // deleted, or when a new LayoutManager is installed. + LayoutManager* GetLayoutManager() const; + void SetLayoutManager(LayoutManager* layout); + + // Right-to-left UI layout functions + + // Indicates whether the UI layout for this view is right-to-left. The view + // has an RTL UI layout if RTL hasn't been disabled for the view and if the + // locale's language is an RTL language. + bool UILayoutIsRightToLeft() const { + return (ui_mirroring_is_enabled_for_rtl_languages_ && + l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT); + } + + // Enables or disables the right-to-left layout for the view. If |enable| is + // true, the layout will become right-to-left only if the locale's language + // is right-to-left. + // + // By default, right-to-left UI layout is enabled for the view and therefore + // this function must be called (with false as the |enable| parameter) in + // order to disable the right-to-left layout property for a specific instance + // of the view. Disabling the right-to-left UI layout is necessary in case a + // UI element will not appear correctly when mirrored. + void EnableUIMirroringForRTLLanguages(bool enable) { + ui_mirroring_is_enabled_for_rtl_languages_ = enable; + } + + // This method determines whether the ChromeCanvas object passed to + // View::Paint() needs to be transformed such that anything drawn on the + // canvas object during View::Paint() is flipped horizontally. + // + // By default, this function returns false (which is the initial value of + // |flip_canvas_on_paint_for_rtl_ui_|). View subclasses that need to paint on + // a flipped ChromeCanvas when the UI layout is right-to-left need to call + // EnableCanvasFlippingForRTLUI(). + bool FlipCanvasOnPaintForRTLUI() const { + return flip_canvas_on_paint_for_rtl_ui_ ? UILayoutIsRightToLeft() : false; + } + + // Enables or disables flipping of the ChromeCanvas during View::Paint(). + // Note that if canvas flipping is enabled, the canvas will be flipped only + // if the UI layout is right-to-left; that is, the canvas will be flipped + // only if UILayoutIsRightToLeft() returns true. + // + // Enabling canvas flipping is useful for leaf views that draw a bitmap that + // needs to be flipped horizontally when the UI layout is right-to-left + // (ChromeViews::Button, for example). This method is helpful for such + // classes because their drawing logic stays the same and they can become + // agnostic to the UI directionality. + void EnableCanvasFlippingForRTLUI(bool enable) { + flip_canvas_on_paint_for_rtl_ui_ = enable; + } + + // Returns the mirrored X position for the view, relative to the parent. If + // the parent view is not mirrored, this function returns bound_.left. + // + // UI mirroring is transparent to most View subclasses and therefore there is + // no need to call this routine from anywhere within your subclass + // implementation. + inline int View::MirroredX() const; + + // Given a rectangle specified in this View's coordinate system, the function + // computes the 'left' value for the mirrored rectangle within this View. If + // the View's UI layout is not right-to-left, then bounds.x() is returned. + // + // UI mirroring is transparent to most View subclasses and therefore there is + // no need to call this routine from anywhere within your subclass + // implementation. + int MirroredLeftPointForRect(const gfx::Rect& rect) const; + + // Given the X coordinate of a point inside the View, this function returns + // the mirrored X coordinate of the point if the View's UI layout is + // right-to-left. If the layout is left-to-right, the same X coordinate is + // returned. + // + // Following are a few examples of the values returned by this function for + // a View with the bounds {0, 0, 100, 100} and a right-to-left layout: + // + // MirroredXCoordinateInsideView(0) -> 100 + // MirroredXCoordinateInsideView(20) -> 80 + // MirroredXCoordinateInsideView(99) -> 1 + int MirroredXCoordinateInsideView(int x) const { + return UILayoutIsRightToLeft() ? GetWidth() - x : x; + } + + // Painting functions + + // Mark the specified rectangle as dirty (needing repaint). If |urgent| is + // true, the view will be repainted when the current event processing is + // done. Otherwise, painting will take place as soon as possible. + virtual void SchedulePaint(const CRect& r, bool urgent); + + // Mark the entire View's bounds as dirty. Painting will occur as soon as + // possible. + virtual void SchedulePaint(); + + // Convenience to schedule a paint given some ints. Painting will occur as + // soon as possible. + virtual void SchedulePaint(int x, int y, int w, int h); + + // Paint the receiving view. g is prepared such as it is in + // receiver's coordinate system. g's state is restored after this + // call so your implementation can change the graphics configuration + // + // Default implementation paints the background if it is defined + // + // Override this method when implementing a new control. + virtual void Paint(ChromeCanvas* canvas); + + // Paint the background if any. This method is called by Paint() and + // should rarely be invoked directly. + virtual void PaintBackground(ChromeCanvas* canvas); + + // Paint the border if any. This method is called by Paint() and + // should rarely be invoked directly. + virtual void PaintBorder(ChromeCanvas* canvas); + + // Paints the focus border (only if the view has the focus). + // This method is called by Paint() and should rarely be invoked directly. + // The default implementation paints a gray border around the view. Override + // it for custom focus effects. + virtual void PaintFocusBorder(ChromeCanvas* canvas); + + // Paint this View immediately. + virtual void PaintNow(); + + // Paint a view without attaching it to this view hierarchy. + // Any view can be painted that way. + // This method set bounds, calls layout and handles clipping properly. The + // provided view can be attached to a parent. The parent will be saved and + // restored. (x, y, width, height) define the floating view bounds + void PaintFloatingView(ChromeCanvas* canvas, View* view, + int x, int y, int w, int h); + + // Tree functions + + // Add a child View. + void AddChildView(View* v); + + // Adds a child View at the specified position. + void AddChildView(int index, View* v); + + // Get the child View at the specified index. + View* GetChildViewAt(int index) const; + + // Remove a child view from this view. v's parent will change to NULL + void RemoveChildView(View *v); + + // Remove all child view from this view. If |delete_views| is true, the views + // are deleted, unless marked as not parent owned. + void RemoveAllChildViews(bool delete_views); + + // Get the number of child Views. + int GetChildViewCount() const; + + // Get the child View at the specified point. + virtual View* GetViewForPoint(const CPoint& point); + + // Get the containing ViewContainer + virtual ViewContainer* GetViewContainer() const; + + // Get the containing RootView + virtual RootView* GetRootView(); + + // Get the parent View + View* GetParent() const { return parent_; } + + // Returns the index of the specified |view| in this view's children, or -1 + // if the specified view is not a child of this view. + int GetChildIndex(View* v) const; + + // Returns true if the specified view is a direct or indirect child of this + // view. + bool IsParentOf(View* v) const; + + // Recursively descends the view tree starting at this view, and returns + // the first child that it encounters that has the given ID. + // Returns NULL if no matching child view is found. + virtual View* GetViewByID(int id) const; + + // Sets and gets the ID for this view. ID should be unique within the subtree + // that you intend to search for it. 0 is the default ID for views. + void SetID(int id); + int GetID() const; + + // A group id is used to tag views which are part of the same logical group. + // Focus can be moved between views with the same group using the arrow keys. + // Groups are currently used to implement radio button mutual exclusion. + void SetGroup(int gid); + int GetGroup() const; + + // If this returns true, the views from the same group can each be focused + // when moving focus with the Tab/Shift-Tab key. If this returns false, + // only the selected view from the group (obtained with + // GetSelectedViewForGroup()) is focused. + virtual bool IsGroupFocusTraversable() const { return true; } + + // Fills the provided vector with all the available views which belong to the + // provided group. + void GetViewsWithGroup(int group_id, std::vector<View*>* out); + + // Return the View that is currently selected in the specified group. + // The default implementation simply returns the first View found for that + // group. + virtual View* GetSelectedViewForGroup(int group_id); + + // Focus support + // + // Returns the view that should be selected next when pressing Tab. + View* GetNextFocusableView(); + + // Returns the view that should be selected next when pressing Shift-Tab. + View* GetPreviousFocusableView(); + + // Sets the component that should be selected next when pressing Tab, and + // makes the current view the precedent view of the specified one. + // Note that by default views are linked in the order they have been added to + // their container. Use this method if you want to modify the order. + // IMPORTANT NOTE: loops in the focus hierarchy are not supported. + void SetNextFocusableView(View* view); + + // Return whether this view can accept the focus. + virtual bool IsFocusable() const; + + // Sets whether this view can accept the focus. + // Note that this is false by default so that a view used as a container does + // not get the focus. + virtual void SetFocusable(bool focusable); + + // Convenience method to retrieve the FocusManager associated with the + // container window that contains this view. This can return NULL if this + // view is not part of a view hierarchy with a ViewContainer. + virtual FocusManager* GetFocusManager(); + + // Sets a keyboard accelerator for that view. When the user presses the + // accelerator key combination, the AcceleratorPressed method is invoked. + // Note that you can set multiple accelerators for a view by invoking this + // method several times. + virtual void AddAccelerator(const Accelerator& accelerator); + + // Removes all the keyboard accelerators for this view. + virtual void ResetAccelerators(); + + // Called when a keyboard accelerator is pressed. + // Derived classes should implement desired behavior and return true if they + // handled the accelerator. + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + return false; + } + + // Called on a view (if it is has focus) before an Accelerator is processed. + // Views that want to override an accelerator should override this method to + // perform the required action and return true, to indicate that the + // accelerator should not be processed any further. + virtual bool OverrideAccelerator(const Accelerator& accelerator) { + return false; + } + + // Returns whether this view currently has the focus. + virtual bool HasFocus(); + + // Accessibility support + // TODO(klink): Move all this out to a AccessibleInfo wrapper class. + // + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. For instance, default action of a button is + // 'Press'. Sets the input string appropriately, and returns true if + // successful. + virtual bool GetAccessibleDefaultAction(std::wstring* action) { + return false; + } + + // Returns a string containing the mnemonic, or the keyboard shortcut, for a + // given control. Sets the input string appropriately, and returns true if + // successful. + virtual bool GetAccessibleKeyboardShortcut(std::wstring* shortcut) { + return false; + } + + // Returns a brief, identifying string, containing a unique, readable name of + // a given control. Sets the input string appropriately, and returns true if + // successful. + virtual bool GetAccessibleName(std::wstring* name) { return false; } + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. Sets the input VARIANT appropriately, and returns true if + // successful. + virtual bool GetAccessibleRole(VARIANT* role) { return false; } + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + virtual bool GetAccessibleState(VARIANT* state) { return false; } + + // Assigns a keyboard shortcut string description to the given control. Needed + // as a View does not know which shortcut will be associated with it until it + // is created to be a certain type. + virtual void SetAccessibleKeyboardShortcut(const std::wstring& shortcut) {} + + // Assigns a string name to the given control. Needed as a View does not know + // which name will be associated with it until it is created to be a + // certain type. + virtual void SetAccessibleName(const std::wstring& name) {} + + // Returns an instance of a wrapper class implementing the (platform-specific) + // accessibility interface for a given View. If one exists, it will be + // re-used, otherwise a new instance will be created. + AccessibleWrapper* GetAccessibleWrapper(); + + // Accessor used to determine if a child view (leaf) has accessibility focus. + // Returns NULL if there are no children, or if none of the children has + // accessibility focus. + virtual ChromeViews::View* GetAccFocusedChildView() { return NULL; } + + // Floating views + // + // A floating view is a view that is used to paint a cell within a parent view + // Floating Views are painted using PaintFloatingView() above. + // + // Floating views can also be lazily created and attached to the view + // hierarchy to process events. To make this possible, each view is given an + // opportunity to create and attach a floating view right before an mouse + // event is processed. + + // Retrieves the id for the floating view at the specified coordinates if any. + // Derived classes that use floating views should implement this method and + // return true if a view has been found and its id set in |id|. + virtual bool GetFloatingViewIDForPoint(int x, int y, int* id); + + // Retrieves the ID of the floating view at the specified |position| and sets + // it in |id|. + // For positions NEXT and PREVIOUS, the specified |starting_id| is used as + // the origin, it is ignored for FIRST and LAST. + // Returns true if an ID was found, false otherwise. + // For CURRENT, the |starting_id| should be set in |id| and true returned if + // the |starting_id| is a valid floating view id. + // Derived classes that use floating views should implement this method and + // return a unique ID for each floating view. + // The default implementation always returns false. + virtual bool EnumerateFloatingViews(FloatingViewPosition position, + int starting_id, + int* id); + + // Creates and attaches the floating view with the specified |id| to this view + // hierarchy and returns it. + // Derived classes that use floating views should implement this method. + // + // NOTE: subclasses implementing this should return NULL if passed an invalid + // id. An invalid ID may be passed in by the focus manager when attempting + // to restore focus. + virtual View* ValidateFloatingViewForID(int id); + + // Whether the focus should automatically be restored to the last focused + // view. Default implementation returns true. + // Derived classes that want to restore focus themselves should override this + // method and return false. + virtual bool ShouldRestoreFloatingViewFocus(); + + // Attach a floating view to the receiving view. The view is inserted + // in the child view list and will behave like a normal view. |id| is the + // floating view id for that view. + void AttachFloatingView(View* v, int id); + + // Return whether a view already has a floating view which bounds intersects + // the provided point. + // + // If the View uses right-to-left UI layout, then the given point is checked + // against the mirrored position of each floating View. + bool HasFloatingViewForPoint(int x, int y); + + // Detach and delete all floating views. Call this method when your model + // or layout changes. + void DetachAllFloatingViews(); + + // Returns the view with the specified |id|, by calling + // ValidateFloatingViewForID if that view has not yet been attached. + virtual View* RetrieveFloatingViewForID(int id); + + // Restores the focus to the previously selected floating view. + virtual void RestoreFloatingViewFocus(); + + // Goes up the parent hierarchy of this view and returns the first floating + // view found. Returns NULL if none were found. + View* RetrieveFloatingViewParent(); + + // Utility functions + + // Note that the utility coordinate conversions functions always operate on + // the mirrored position of the child Views if the parent View uses a + // right-to-left UI layout. + + // Convert a point from source coordinate system to dst coordinate system. + // + // source is a parent or a child of dst, directly or transitively. + // If source and dst are not in the same View hierarchy, the result is + // undefined. + // Source can be NULL in which case it means the screen coordinate system + static void ConvertPointToView(View* src, + View* dst, + gfx::Point* point); + // WARNING: DEPRECATED. Will be removed once everything is converted to + // gfx::Point. Don't add code that use this overload. + static void ConvertPointToView(View* src, + View* dst, + CPoint* point); + + // Convert a point from the coordinate system of a View to that of the + // ViewContainer. This is useful for example when sizing HWND children + // of the ViewContainer that don't know about the View hierarchy and need + // to be placed relative to the ViewContainer that is their parent. + static void ConvertPointToViewContainer(View* src, CPoint* point); + + // Convert a point from a view ViewContainer to a View dest + static void ConvertPointFromViewContainer(View *dest, CPoint *p); + + // Convert a point from the coordinate system of a View to that of the + // screen. This is useful for example when placing popup windows. + static void ConvertPointToScreen(View* src, CPoint* point); + + // Event Handlers + + // This method is invoked when the user clicks on this view. + // The provided event is in the receiver's coordinate system. + // + // Return true if you processed the event and want to receive subsequent + // MouseDraggged and MouseReleased events. This also stops the event from + // bubbling. If you return false, the event will bubble through parent + // views. + // + // If you remove yourself from the tree while processing this, event bubbling + // stops as if you returned true, but you will not receive future events. + // The return value is ignored in this case. + // + // Default implementation returns true if a ContextMenuController has been + // set, false otherwise. Override as needed. + // + virtual bool OnMousePressed(const MouseEvent& event); + + // This method is invoked when the user clicked on this control. + // and is still moving the mouse with a button pressed. + // The provided event is in the receiver's coordinate system. + // + // Return true if you processed the event and want to receive + // subsequent MouseDragged and MouseReleased events. + // + // Default implementation returns true if a ContextMenuController has been + // set, false otherwise. Override as needed. + // + virtual bool OnMouseDragged(const MouseEvent& event); + + // This method is invoked when the user releases the mouse + // button. The event is in the receiver's coordinate system. + // + // If canceled is true it indicates the mouse press/drag was canceled by a + // system/user gesture. + // + // Default implementation notifies the ContextMenuController is appropriate. + // Subclasses that wish to honor the ContextMenuController should invoke + // super. + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + + // This method is invoked when the mouse is above this control + // The event is in the receiver's coordinate system. + // + // Default implementation does nothing. Override as needed. + virtual void OnMouseMoved(const MouseEvent& e); + + // This method is invoked when the mouse enters this control. + // + // Default implementation does nothing. Override as needed. + virtual void OnMouseEntered(const MouseEvent& event); + + // This method is invoked when the mouse exits this control + // The provided event location is always (0, 0) + // Default implementation does nothing. Override as needed. + virtual void OnMouseExited(const MouseEvent& event); + + // Set the MouseHandler for a drag session. + // + // A drag session is a stream of mouse events starting + // with a MousePressed event, followed by several MouseDragged + // events and finishing with a MouseReleased event. + // + // This method should be only invoked while processing a + // MouseDragged or MouseReleased event. + // + // All further mouse dragged and mouse up events will be sent + // the MouseHandler, even if it is reparented to another window. + // + // The MouseHandler is automatically cleared when the control + // comes back from processing the MouseReleased event. + // + // Note: if the mouse handler is no longer connected to a + // view hierarchy, events won't be sent. + // + virtual void SetMouseHandler(View* new_mouse_handler); + + // Request the keyboard focus. The receiving view will become the + // focused view. + virtual void RequestFocus(); + + // Invoked when a view is about to gain focus + virtual void WillGainFocus(); + + // Invoked when a view just gained focus. + virtual void DidGainFocus(); + + // Invoked when a view is about lose focus + virtual void WillLoseFocus(); + + // Invoked when a view is about to be requested for focus due to the focus + // traversal. Reverse is this request was generated going backward + // (Shift-Tab). + virtual void AboutToRequestFocusFromTabTraversal(bool reverse) { } + + // Invoked when a key is pressed or released. + // Subclasser should return true if the event has been processed and false + // otherwise. If the event has not been processed, the parent will be given a + // chance. + virtual bool OnKeyPressed(const KeyEvent& e); + virtual bool OnKeyReleased(const KeyEvent& e); + + // Whether the view wants to receive Tab and Shift-Tab key events. + // If false, Tab and Shift-Tabs key events are used for focus traversal and + // are not sent to the view. If true, the events are sent to the view and not + // used for focus traversal. + // This implementation returns false (so that by default views handle nicely + // the keyboard focus traversal). + virtual bool CanProcessTabKeyEvents(); + + // Invoked when the user uses the mousewheel. Implementors should return true + // if the event has been processed and false otherwise. This message is sent + // if the view is focused. If the event has not been processed, the parent + // will be given a chance. + virtual bool OnMouseWheel(const MouseWheelEvent& e); + + // Drag and drop functions. + + // Set/get the DragController. See description of DragController for more + // information. + void SetDragController(DragController* drag_controller); + DragController* GetDragController(); + + // During a drag and drop session when the mouse moves the view under the + // mouse is queried to see if it should be a target for the drag and drop + // session. A view indicates it is a valid target by returning true from + // CanDrop. If a view returns true from CanDrop, + // OnDragEntered is sent to the view when the mouse first enters the view, + // as the mouse moves around within the view OnDragUpdated is invoked. + // If the user releases the mouse over the view and OnDragUpdated returns a + // valid drop, then OnPerformDrop is invoked. If the mouse moves outside the + // view or over another view that wants the drag, OnDragExited is invoked. + // + // Similar to mouse events, the deepest view under the mouse is first checked + // if it supports the drop (Drop). If the deepest view under + // the mouse does not support the drop, the ancestors are walked until one + // is found that supports the drop. + + // A view that supports drag and drop must override this and return true if + // data contains a type that may be dropped on this view. + virtual bool CanDrop(const OSExchangeData& data); + + // OnDragEntered is invoked when the mouse enters this view during a drag and + // drop session and CanDrop returns true. This is immediately + // followed by an invocation of OnDragUpdated, and eventually one of + // OnDragExited or OnPerformDrop. + virtual void OnDragEntered(const DropTargetEvent& event); + + // Invoked during a drag and drop session while the mouse is over the view. + // This should return a bitmask of the DragDropTypes::DragOperation supported + // based on the location of the event. Return 0 to indicate the drop should + // not be accepted. + virtual int OnDragUpdated(const DropTargetEvent& event); + + // Invoked during a drag and drop session when the mouse exits the views, or + // when the drag session was canceled and the mouse was over the view. + virtual void OnDragExited(); + + // Invoked during a drag and drop session when OnDragUpdated returns a valid + // operation and the user release the mouse. + virtual int OnPerformDrop(const DropTargetEvent& event); + + // Returns true if the mouse was dragged enough to start a drag operation. + // delta_x and y are the distance the mouse was dragged. + static bool ExceededDragThreshold(int delta_x, int delta_y); + + // This method is the main entry point to process paint for this + // view and its children. This method is called by the painting + // system. You should call this only if you want to draw a sub tree + // inside a custom graphics. + // To customize painting override either the Paint or PaintChildren method, + // not this one. + virtual void ProcessPaint(ChromeCanvas* canvas); + + // Paint the View's child Views, in reverse order. + virtual void PaintChildren(ChromeCanvas* canvas); + + // Sets the ContextMenuController. Setting this to non-null makes the View + // process mouse events. + void SetContextMenuController(ContextMenuController* menu_controller); + ContextMenuController* GetContextMenuController() { + return context_menu_controller_; + } + + // Set the background. The background is owned by the view after this call. + virtual void SetBackground(Background* b); + + // Return the background currently in use or NULL. + virtual const Background* GetBackground() const; + + // Set the border. The border is owned by the view after this call. + virtual void SetBorder(Border* b); + + // Return the border currently in use or NULL. + virtual const Border* GetBorder() const; + + // Returns the insets of the current border. If there is no border an empty + // insets is returned. + gfx::Insets GetInsets() const; + + // Return the cursor that should be used for this view or NULL if + // the default cursor should be used. The provided point is in the + // receiver's coordinate system. + virtual HCURSOR GetCursorForPoint(Event::EventType event_type, int x, int y); + + // Convenience to test whether a point is within this view's bounds + virtual bool HitTest(const CPoint &l) const; + + // Gets the tooltip for this View. If the View does not have a tooltip, + // return false. If the View does have a tooltip, copy the tooltip into + // the supplied string and return true. + // Any time the tooltip text that a View is displaying changes, it must + // invoke TooltipTextChanged. + // The x/y provide the coordinates of the mouse (relative to this view). + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + // Returns the location (relative to this View) for the text on the tooltip + // to display. If false is returned (the default), the tooltip is placed at + // a default position. + virtual bool GetTooltipTextOrigin(int x, int y, CPoint* loc); + + // Set whether this view is owned by its parent. A view that is owned by its + // parent is automatically deleted when the parent is deleted. The default is + // true. Set to false if the view is owned by another object and should not + // be deleted by its parent. + void SetParentOwned(bool f); + + // Return whether a view is owned by its parent. See SetParentOwned() + bool IsParentOwned() const; + + // Return the receiving view's class name. A view class is a string which + // uniquely identifies the view class. It is intended to be used as a way to + // find out during run time if a view can be safely casted to a specific view + // subclass. The default implementation returns kViewClassName. + virtual std::string GetClassName() const; + + // Returns the visible bounds of the receiver in the receivers coordinate + // system. + // + // When traversing the View hierarchy in order to compute the bounds, the + // function takes into account the mirroring setting for each View and + // therefore it will return the mirrored version of the visible bounds if + // need be. + gfx::Rect GetVisibleBounds(); + + // Subclasses that contain traversable children that are not directly + // accessible through the children hierarchy should return the associated + // FocusTraversable for the focus traversal to work properly. + virtual FocusTraversable* GetFocusTraversable() { return NULL; } + +#ifndef NDEBUG + // Debug method that logs the view hierarchy to the output. + void PrintViewHierarchy(); + + // Debug method that logs the focus traversal hierarchy to the output. + void PrintFocusHierarchy(); +#endif + + // The following methods are used by ScrollView to determine the amount + // to scroll relative to the visible bounds of the view. For example, a + // return value of 10 indicates the scrollview should scroll 10 pixels in + // the appropriate direction. + // + // Each method takes the following parameters: + // + // is_horizontal: if true, scrolling is along the horizontal axis, otherwise + // the vertical axis. + // is_positive: if true, scrolling is by a positive amount. Along the + // vertical axis scrolling by a positive amount equates to + // scrolling down. + // + // The return value should always be positive and gives the number of pixels + // to scroll. ScrollView interprets a return value of 0 (or negative) + // to scroll by a default amount. + // + // See VariableRowHeightScrollHelper and FixedRowHeightScrollHelper for + // implementations of common cases. + virtual int GetPageScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive); + virtual int GetLineScrollIncrement(ScrollView* scroll_view, + bool is_horizontal, bool is_positive); + + protected: + // This View's bounds in the parent coordinate system. + CRect bounds_; + + // The id of this View. Used to find this View. + int id_; + + // The group of this view. Some view subclasses use this id to find other + // views of the same group. For example radio button uses this information + // to find other radio buttons. + int group_; + +#ifndef NDEBUG + // Returns true if the View is currently processing a paint. + virtual bool IsProcessingPaint() const; +#endif + + // This method is invoked when the tree changes. + // + // When a view is removed, it is invoked for all children and grand + // children. For each of these views, a notification is sent to the + // view and all parents. + // + // When a view is added, a notification is sent to the view, all its + // parents, and all its children (and grand children) + // + // Default implementation does nothing. Override to perform operations + // required when a view is added or removed from a view hierarchy + // + // parent is the new or old parent. Child is the view being added or + // removed. + // + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + + // When SetVisible() changes the visibility of a view, this method is + // invoked for that view as well as all the children recursively. + virtual void VisibilityChanged(View* starting_from, bool is_visible); + + // Views must invoke this when the tooltip text they are to display changes. + void TooltipTextChanged(); + + // Actual implementation of GetViewForPoint. + virtual View* GetViewForPoint(const CPoint& point, bool can_create_floating); + + // Sets whether this view wants notification when its visible bounds relative + // to the root view changes. If true, this view is notified any time the + // origin of one its ancestors changes, or the portion of the bounds not + // obscured by ancestors changes. The default is false. + void SetNotifyWhenVisibleBoundsInRootChanges(bool value); + bool GetNotifyWhenVisibleBoundsInRootChanges(); + + // Notification that this views visible bounds, relative to the RootView + // has changed. The visible bounds corresponds to the region of the + // view not obscured by other ancestors. + virtual void VisibleBoundsInRootChanged() {} + + // Sets the keyboard focus to this View. The correct way to set the focus is + // to call RequestFocus() on the view. This method is called when the focus is + // set and gives an opportunity to subclasses to perform any extra focus steps + // (for example native component set the native focus on their native + // component). The default behavior is to set the native focus on the root + // view container, which is what is appropriate for views that have no native + // window associated with them (so the root view gets the keyboard messages). + virtual void Focus(); + + // Heavyweight views (views that hold a native control) should return the + // window for that control. + virtual HWND GetNativeControlHWND() { return NULL; } + + // Invoked when a key is pressed before the key event is processed by the + // focus manager for accelerators. This gives a chance to the view to + // override an accelerator. Subclasser should return false if they want to + // process the key event and not have it translated to an accelerator (if + // any). In that case, OnKeyPressed will subsequently be invoked for that + // event. + virtual bool ShouldLookupAccelerators(const KeyEvent& e) { return true; } + + // A convenience method for derived classes which have floating views with IDs + // that are consecutive numbers in an interval [|low_bound|, |high_bound|[. + // They can call this method in their EnumerateFloatingViews implementation. + // If |ascending_order| is true, the first id is |low_bound|, the next after + // id n is n + 1, and so on. If |ascending_order| is false, the order is + // reversed, first id is |high_bound|, the next id after id n is n -1... + static bool EnumerateFloatingViewsForInterval(int low_bound, int high_bound, + bool ascending_order, + FloatingViewPosition position, + int starting_id, + int* id); + + // These are cover methods that invoke the method of the same name on + // the DragController. Subclasses may wish to override rather than install + // a DragController. + // See DragController for a description of these methods. + virtual int GetDragOperations(int press_x, int press_y); + virtual void WriteDragData(int press_x, int press_y, OSExchangeData* data); + + // Invoked from DoDrag after the drag completes. This implementation does + // nothing, and is intended for subclasses to do cleanup. + virtual void OnDragDone(); + + // Returns whether we're in the middle of a drag session that was initiated + // by us. + bool InDrag(); + + // Whether this view is enabled. + bool enabled_; + + // Whether the view can be focused. + bool focusable_; + + private: + friend class RootView; + friend class FocusManager; + friend class ViewStorage; + + // Used to track a drag. RootView passes this into + // ProcessMousePressed/Dragged. + struct DragInfo { + // Sets possible_drag to false and start_x/y to 0. This is invoked by + // RootView prior to invoke ProcessMousePressed. + void Reset(); + + // Sets possible_drag to true and start_x/y to the specified coordinates. + // This is invoked by the target view if it detects the press may generate + // a drag. + void PossibleDrag(int x, int y); + + // Whether the press may generate a drag. + bool possible_drag; + + // Coordinates of the mouse press. + int start_x; + int start_y; + }; + + // RootView invokes these. These in turn invoke the appropriate OnMouseXXX + // method. If a drag is detected, DoDrag is invoked. + bool ProcessMousePressed(const MouseEvent& e, DragInfo* drop_info); + bool ProcessMouseDragged(const MouseEvent& e, DragInfo* drop_info); + void ProcessMouseReleased(const MouseEvent& e, bool canceled); + + // Starts a drag and drop operation originating from this view. This invokes + // WriteDragData to write the data and GetDragOperations to determine the + // supported drag operations. When done, OnDragDone is invoked. + void DoDrag(const ChromeViews::MouseEvent& e, int press_x, int press_y); + + // Adds a child View at the specified position. |floating_view| should be true + // if the |v| is a floating view. + void AddChildView(int index, View* v, bool floating_view); + + // Removes |view| from the hierarchy tree. If |update_focus_cycle| is true, + // the next and previous focusable views of views pointing to this view are + // updated. If |update_tool_tip| is true, the tooltip is updated. If + // |delete_removed_view| is true, the view is also deleted (if it is parent + // owned). + void DoRemoveChildView(View* view, + bool update_focus_cycle, + bool update_tool_tip, + bool delete_removed_view); + + // Sets the parent View. This is called automatically by AddChild and is + // thus private. + void SetParent(View *parent); + + // Call ViewHierarchyChanged for all child views on all parents + void PropagateRemoveNotifications(View* parent); + + // Call ViewHierarchyChanged for all children + void PropagateAddNotifications(View* parent, View* child); + + // Call VisibilityChanged() recursively for all children. + void PropagateVisibilityNotifications(View* from, bool is_visible); + + // Takes care of registering/unregistering accelerators if + // |register_accelerators| true and calls ViewHierarchyChanged(). + void ViewHierarchyChangedImpl(bool register_accelerators, + bool is_add, View *parent, View *child); + + // This is the actual implementation for ConvertPointToView() + // Attempts a parent -> child conversion and then a + // child -> parent conversion if try_other_direction is true + static void ConvertPointToView(View* src, + View *dst, + gfx::Point* point, + bool try_other_direction); + + // Propagates UpdateTooltip() to the TooltipManager for the ViewContainer. + // This must be invoked any time the View hierarchy changes in such a way + // the view under the mouse differs. For example, if the bounds of a View is + // changed, this is invoked. Similarly, as Views are added/removed, this + // is invoked. + void UpdateTooltip(); + + // Recursively descends through all descendant views, + // registering/unregistering all views that want visible bounds in root + // view notification. + static void RegisterChildrenForVisibleBoundsNotification(RootView* root, + View* view); + static void UnregisterChildrenForVisibleBoundsNotification(RootView* root, + View* view); + + // Adds/removes view to the list of descendants that are notified any time + // this views location and possibly size are changed. + void AddDescendantToNotify(View* view); + void RemoveDescendantToNotify(View* view); + + // Initialize the previous/next focusable views of the specified view relative + // to the view at the specified index. + void InitFocusSiblings(View* view, int index); + + // Actual implementation of PrintFocusHierarchy. + void PrintViewHierarchyImp(int indent); + void PrintFocusHierarchyImp(int indent); + + // Registers/unregister this view's keyboard accelerators with the + // FocusManager. + void RegisterAccelerators(); + void UnregisterAccelerators(); + + // Returns the number of children that are actually attached floating views. + int GetFloatingViewCount() const; + + // Returns the id for this floating view. + int GetFloatingViewID(); + + // Returns whether this view is a floating view. + bool IsFloatingView(); + + // Sets in |path| the path in the view hierarchy from |start| to |end| (the + // path is the list of indexes in each view's children to get from |start| + // to |end|). + // Returns true if |start| and |view| are connected and the |path| has been + // retrieved succesfully, false otherwise. + static bool GetViewPath(View* start, View* end, std::vector<int>* path); + + // Returns the view at the end of the specified |path|, starting at the + // |start| view. + static View* GetViewForPath(View* start, const std::vector<int>& path); + + // This view's parent + View *parent_; + + // This view's children. + typedef std::vector<View*> ViewList; + ViewList child_views_; + + // List of floating children. A floating view is always referenced by + // child_views_ and will be deleted on destruction just like any other + // child view. + ViewList floating_views_; + + // Maps a floating view to its floating view id. + std::map<View*, int> floating_views_ids_; + + // Whether we want the focus to be restored. This is used to store/restore + // focus for floating views. + bool should_restore_focus_; + + // The View's LayoutManager defines the sizing heuristics applied to child + // Views. The default is absolute positioning according to bounds_. + scoped_ptr<LayoutManager> layout_manager_; + + // Visible state + bool is_visible_; + + // Background + Background* background_; + + // Border. + Border* border_; + + // Whether this view is owned by its parent. + bool is_parent_owned_; + + // See SetNotifyWhenVisibleBoundsInRootChanges. + bool notify_when_visible_bounds_in_root_changes_; + + // Whether or not RegisterViewForVisibleBoundsNotification on the RootView + // has been invoked. + bool registered_for_visible_bounds_notification_; + + // List of descendants wanting notification when their visible bounds change. + scoped_ptr<ViewList> descendants_to_notify_; + + // Next view to be focused when the Tab key is pressed. + View* next_focusable_view_; + + // Next view to be focused when the Shift-Tab key combination is pressed. + View* previous_focusable_view_; + + // The list of accelerators. + scoped_ptr<std::vector<Accelerator>> accelerators_; + + // The task used to restore automatically the focus to the last focused + // floating view. + RestoreFocusTask* restore_focus_view_task_; + + // The menu controller. + ContextMenuController* context_menu_controller_; + + // The accessibility implementation for this View. + scoped_ptr<AccessibleWrapper> accessibility_; + + DragController* drag_controller_; + + // Indicates whether or not the view is going to be mirrored (that is, use a + // right-to-left UI layout) if the locale's language is a right-to-left + // language like Arabic or Hebrew. + bool ui_mirroring_is_enabled_for_rtl_languages_; + + // Indicates whether or not the ChromeCanvas object passed to View::Paint() + // is going to be flipped horizontally (using the appropriate transform) on + // right-to-left locales for this View. + bool flip_canvas_on_paint_for_rtl_ui_; + + DISALLOW_EVIL_CONSTRUCTORS(View); +}; + +} + +#endif // CHROME_VIEWS_VIEW_H__ diff --git a/chrome/views/view_container.h b/chrome/views/view_container.h new file mode 100644 index 0000000..daf106c --- /dev/null +++ b/chrome/views/view_container.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef CHROME_VIEWS_VIEW_CONTAINER_H__ +#define CHROME_VIEWS_VIEW_CONTAINER_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> + +namespace ChromeViews { + +class RootView; +class TooltipManager; +class Accelerator; + +///////////////////////////////////////////////////////////////////////////// +// +// ViewContainer class +// +// ViewContainer is an abstract class that defines the API that should +// be implemented by a CWindow / HWND implementation in order to host a view +// hierarchy. +// +// ViewContainer wraps a hierarchy of View objects (see view.h) that +// implement painting and flexible layout within the bounds of the +// ViewContainer's window. +// +// The ViewContainer is responsible for handling various system events and +// forwarding them to the appropriate view. +// +///////////////////////////////////////////////////////////////////////////// + +class ViewContainer { + public: + + // Returns the bounds of this container in the screen coordinate system. + // If the receiving view container is a frame which is larger than its + // client area, this method returns the client area if including_frame is + // false and the frame bounds otherwise. If the receiving view container + // is not a frame, including_frame is ignored. + virtual void GetBounds(CRect *out, bool including_frame) const = 0; + + // Moves this view container to the front of the Z-Order + // If should_activate is TRUE, the window should also become the active + // window + virtual void MoveToFront(bool should_activate) = 0; + + // Returns the Window HWND associated with this container + virtual HWND GetHWND() const = 0; + + // Forces a paint of a specified rectangle immediately. + virtual void PaintNow(const CRect& update_rect) = 0; + + // Returns the RootView contained by this container + virtual RootView* GetRootView() = 0; + + // Returns whether the view container is visible to the user + virtual bool IsVisible() = 0; + + // Returns whether the view container is the currently active window. + virtual bool IsActive() = 0; + + // Returns the TooltipManager for this ViewContainer. If this ViewContainer + // does not support tooltips, NULL is returned. + virtual TooltipManager* GetTooltipManager() { + return NULL; + } + + // Returns the accelerator given a command id. Returns false if there is + // no accelerator associated with a given id, which is a common condition. + virtual bool GetAccelerator(int cmd_id, + ChromeViews::Accelerator* accelerator) = 0; +}; + + + +} + +#endif // CHROME_VIEWS_VIEW_CONTAINER_H__ diff --git a/chrome/views/view_menu_delegate.h b/chrome/views/view_menu_delegate.h new file mode 100644 index 0000000..a779945 --- /dev/null +++ b/chrome/views/view_menu_delegate.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef CHROME_VIEWS_VIEW_MENU_DELEGATE_H__ +#define CHROME_VIEWS_VIEW_MENU_DELEGATE_H__ + +#include <windows.h> + +namespace ChromeViews { + +class View; + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuDelegate +// +// An interface that allows a component to tell a View about a menu that it +// has constructed that the view can show (e.g. for MenuButton views, or as a +// context menu.) +// +//////////////////////////////////////////////////////////////////////////////// +class ViewMenuDelegate { + public: + // Create and show a menu at the specified position. Source is the view the + // ViewMenuDelegate was set on. + virtual void RunMenu(ChromeViews::View* source, + const CPoint& pt, + HWND hwnd) = 0; +}; + +} // namespace + +#endif // CHROME_VIEWS_VIEW_MENU_DELEGATE_H__ diff --git a/chrome/views/view_storage.cc b/chrome/views/view_storage.cc new file mode 100644 index 0000000..3969d7e --- /dev/null +++ b/chrome/views/view_storage.cc @@ -0,0 +1,231 @@ +// 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 "chrome/views/view_storage.h" + +#include <algorithm> + +#include "chrome/common/notification_types.h" +#include "chrome/common/stl_util-inl.h" + +namespace ChromeViews { + +ViewStorage* ViewStorage::shared_instance_ = NULL; + +// 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() { + if (shared_instance_) + return shared_instance_; + + shared_instance_ = new ViewStorage(); + return shared_instance_; +} + +// static +void ViewStorage::DeleteSharedInstance() { + if (!shared_instance_) + return; + delete shared_instance_; + shared_instance_ = NULL; +} + +ViewStorage::ViewStorage() : view_storage_next_id_(0) { + NotificationService::current()-> + AddObserver(this, NOTIFY_VIEW_REMOVED, NotificationService::AllSources()); +} + +ViewStorage::~ViewStorage() { + NotificationService::current()-> + RemoveObserver(this, NOTIFY_VIEW_REMOVED, + NotificationService::AllSources()); + + 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::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); + } +} + +void ViewStorage::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NOTIFY_VIEW_REMOVED); + + // Let's first retrieve the ids for that view. + std::map<View*, std::vector<int>*>::iterator ids_iter = + view_to_ids_.find(Source<View>(source).ptr()); + + 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); +} + +} diff --git a/chrome/views/view_storage.h b/chrome/views/view_storage.h new file mode 100644 index 0000000..679795f --- /dev/null +++ b/chrome/views/view_storage.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef CHROME_VIEWS_VIEW_STORAGE_H__ +#define CHROME_VIEWS_VIEW_STORAGE_H__ + +#include "chrome/common/notification_service.h" +#include "chrome/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 ChromeViews { + +struct ViewLocationInfo; + +class ViewStorage : public NotificationObserver { + public: + // Returns the global ViewStorage instance. + // It is guaranted to be non NULL. + static ViewStorage* GetSharedInstance(); + + // Deletes the global instance of the ViewStorage. + static void DeleteSharedInstance(); + + // 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); + + private: + ViewStorage(); + ~ViewStorage(); + + // NotificationObserver method. + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // 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_; + + // The singleton instance. + static ViewStorage* shared_instance_; + + DISALLOW_EVIL_CONSTRUCTORS(ViewStorage); +}; + +} + +#endif // #ifndef CHROME_VIEWS_VIEW_STORAGE_H__ diff --git a/chrome/views/view_unittest.cc b/chrome/views/view_unittest.cc new file mode 100644 index 0000000..0cb5af1 --- /dev/null +++ b/chrome/views/view_unittest.cc @@ -0,0 +1,534 @@ +// 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 <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> +#include <atlcrack.h> +#include <atlwin.h> + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/background.h" +#include "chrome/views/event.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ViewTest : public testing::Test { + public: + ViewTest() { + OleInitialize(NULL); + } + + ~ViewTest() { + OleUninitialize(); + } +}; + +// Paints the RootView. +void PaintRootView(ChromeViews::RootView* root, bool empty_paint) { + if (!empty_paint) { + root->PaintNow(); + } else { + // User isn't logged in, so that PaintNow will generate an empty rectangle. + // Invoke paint directly. + CRect paint_rect = root->GetScheduledPaintRect(); + ChromeCanvas canvas(paint_rect.Width(), paint_rect.Height(), true); + canvas.TranslateInt(-paint_rect.left, -paint_rect.top); + canvas.ClipRectInt(0, 0, paint_rect.Width(), paint_rect.Height()); + root->ProcessPaint(&canvas); + } +} + +/* +typedef CWinTraits<WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS> CVTWTraits; + +// A trivial window implementation that tracks whether or not it has been +// painted. This is used by the painting test to determine if paint will result +// in an empty region. +class EmptyWindow : public CWindowImpl<EmptyWindow, + CWindow, + CVTWTraits> { + public: + DECLARE_FRAME_WND_CLASS(L"Chrome_ChromeViewsEmptyWindow", 0) + + BEGIN_MSG_MAP_EX(EmptyWindow) + MSG_WM_PAINT(OnPaint) + END_MSG_MAP() + + EmptyWindow::EmptyWindow(const CRect& bounds) : empty_paint_(false) { + Create(NULL, static_cast<RECT>(bounds)); + ShowWindow(SW_SHOW); + } + + EmptyWindow::~EmptyWindow() { + ShowWindow(SW_HIDE); + DestroyWindow(); + } + + void EmptyWindow::OnPaint(HDC dc) { + PAINTSTRUCT ps; + HDC paint_dc = BeginPaint(&ps); + if (!empty_paint_ && (ps.rcPaint.top - ps.rcPaint.bottom) == 0 && + (ps.rcPaint.right - ps.rcPaint.left) == 0) { + empty_paint_ = true; + } + EndPaint(&ps); + } + + bool empty_paint() { + return empty_paint_; + } + + private: + bool empty_paint_; + + DISALLOW_EVIL_CONSTRUCTORS(EmptyWindow); +}; +*/ +} + +using namespace ChromeViews; + +//////////////////////////////////////////////////////////////////////////////// +// +// A view subclass for testing purpose +// +//////////////////////////////////////////////////////////////////////////////// +class TestView : public View { + public: + TestView() { + } + + virtual ~TestView() {} + + // Reset all test state + void Reset() { + did_change_bounds_ = false; + child_added_ = false; + child_removed_ = false; + last_mouse_event_type_ = 0; + location_.x = 0; + location_.y = 0; + last_clip_.setEmpty(); + } + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual void Paint(ChromeCanvas* canvas); + + // DidChangeBounds test + bool did_change_bounds_; + CRect previous_bounds_; + CRect new_bounds_; + + // AddRemoveNotifications test + bool child_added_; + bool child_removed_; + View* parent_; + View* child_; + + // MouseEvent + int last_mouse_event_type_; + CPoint location_; + + // Painting + SkRect last_clip_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// DidChangeBounds +//////////////////////////////////////////////////////////////////////////////// + +void TestView::DidChangeBounds(const CRect& previous, const CRect& current) { + did_change_bounds_ = true; + previous_bounds_ = previous; + new_bounds_ = current; +} + +TEST_F(ViewTest, DidChangeBounds) { + TestView* v = new TestView(); + + CRect prev_rect(0, 0, 200, 200); + CRect new_rect(100, 100, 250, 250); + + v->SetBounds(prev_rect); + v->Reset(); + + v->SetBounds(new_rect); + EXPECT_EQ(v->did_change_bounds_, true); + EXPECT_EQ(v->previous_bounds_, prev_rect); + EXPECT_EQ(v->new_bounds_, new_rect); + + CRect r; + v->GetBounds(&r); + EXPECT_EQ(r, new_rect); + delete v; +} + + +//////////////////////////////////////////////////////////////////////////////// +// AddRemoveNotifications +//////////////////////////////////////////////////////////////////////////////// + +void TestView::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (is_add) { + child_added_ = true; + } else { + child_removed_ = true; + } + parent_ = parent; + child_ = child; +} + +TEST_F(ViewTest, AddRemoveNotifications) { + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 300, 300); + + TestView* v2 = new TestView(); + v2->SetBounds(0, 0, 300, 300); + + TestView* v3 = new TestView(); + v3->SetBounds(0, 0, 300, 300); + + // Add a child. Make sure both v2 and v3 receive the right + // notification + v2->Reset(); + v3->Reset(); + v2->AddChildView(v3); + EXPECT_EQ(v2->child_added_, true); + EXPECT_EQ(v2->parent_, v2); + EXPECT_EQ(v2->child_, v3); + + EXPECT_EQ(v3->child_added_, true); + EXPECT_EQ(v3->parent_, v2); + EXPECT_EQ(v3->child_, v3); + + // Add v2 and transitively v3 to v1. Make sure that all views + // received the right notification + + v1->Reset(); + v2->Reset(); + v3->Reset(); + v1->AddChildView(v2); + + EXPECT_EQ(v1->child_added_, true); + EXPECT_EQ(v1->child_, v2); + EXPECT_EQ(v1->parent_, v1); + + EXPECT_EQ(v2->child_added_, true); + EXPECT_EQ(v2->child_, v2); + EXPECT_EQ(v2->parent_, v1); + + EXPECT_EQ(v3->child_added_, true); + EXPECT_EQ(v3->child_, v2); + EXPECT_EQ(v3->parent_, v1); + + // Remove v2. Make sure all views received the right notification + v1->Reset(); + v2->Reset(); + v3->Reset(); + v1->RemoveChildView(v2); + + EXPECT_EQ(v1->child_removed_, true); + EXPECT_EQ(v1->parent_, v1); + EXPECT_EQ(v1->child_, v2); + + EXPECT_EQ(v2->child_removed_, true); + EXPECT_EQ(v2->parent_, v1); + EXPECT_EQ(v2->child_, v2); + + EXPECT_EQ(v3->child_removed_, true); + EXPECT_EQ(v3->parent_, v1); + EXPECT_EQ(v3->child_, v3); + + // Clean-up + delete v1; + delete v2; // This also deletes v3 (child of v2). +} + +//////////////////////////////////////////////////////////////////////////////// +// MouseEvent +//////////////////////////////////////////////////////////////////////////////// + +bool TestView::OnMousePressed(const MouseEvent& event) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); + return true; +} + +bool TestView::OnMouseDragged(const MouseEvent& event) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); + return true; +} + +void TestView::OnMouseReleased(const MouseEvent& event, bool canceled) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); +} + +TEST_F(ViewTest, MouseEvent) { + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 300, 300); + + TestView* v2 = new TestView(); + v2->SetBounds (100, 100, 100, 100); + + ChromeViews::Window window; + window.set_delete_on_destroy(false); + window.set_window_style(WS_OVERLAPPEDWINDOW); + window.Init(NULL, gfx::Rect(50, 50, 650, 650), NULL, NULL); + RootView* root = window.GetRootView(); + + root->AddChildView(v1); + v1->AddChildView(v2); + + v1->Reset(); + v2->Reset(); + + MouseEvent pressed(Event::ET_MOUSE_PRESSED, + 110, + 120, + Event::EF_LEFT_BUTTON_DOWN); + root->OnMousePressed(pressed); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_PRESSED); + EXPECT_EQ(v2->location_.x, 10); + EXPECT_EQ(v2->location_.y, 20); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + // Drag event out of bounds. Should still go to v2 + v1->Reset(); + v2->Reset(); + MouseEvent dragged(Event::ET_MOUSE_DRAGGED, + 50, + 40, + Event::EF_LEFT_BUTTON_DOWN); + root->OnMouseDragged(dragged); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_DRAGGED); + EXPECT_EQ(v2->location_.x, -50); + EXPECT_EQ(v2->location_.y, -60); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + // Releasted event out of bounds. Should still go to v2 + v1->Reset(); + v2->Reset(); + MouseEvent released(Event::ET_MOUSE_RELEASED, 0, 0, 0); + root->OnMouseDragged(released); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_RELEASED); + EXPECT_EQ(v2->location_.x, -100); + EXPECT_EQ(v2->location_.y, -100); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + window.CloseNow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Painting +//////////////////////////////////////////////////////////////////////////////// + +void TestView::Paint(ChromeCanvas* canvas) { + canvas->getClipBounds(&last_clip_); +} + +void CheckRect(const SkRect& check_rect, const SkRect& target_rect) { + EXPECT_EQ(target_rect.fLeft, check_rect.fLeft); + EXPECT_EQ(target_rect.fRight, check_rect.fRight); + EXPECT_EQ(target_rect.fTop, check_rect.fTop); + EXPECT_EQ(target_rect.fBottom, check_rect.fBottom); +} + +/* This test is disabled because it is flakey on some systems. +TEST_F(ViewTest, Painting) { + // Determine if InvalidateRect generates an empty paint rectangle. + EmptyWindow paint_window(CRect(50, 50, 650, 650)); + paint_window.RedrawWindow(CRect(0, 0, 600, 600), NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + bool empty_paint = paint_window.empty_paint(); + + ChromeViews::Window window; + window.set_delete_on_destroy(false); + window.set_window_style(WS_OVERLAPPEDWINDOW); + window.Init(NULL, gfx::Rect(50, 50, 650, 650), NULL, NULL); + RootView* root = window.GetRootView(); + + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 650, 650); + root->AddChildView(v1); + + TestView* v2 = new TestView(); + v2->SetBounds(10, 10, 80, 80); + v1->AddChildView(v2); + + TestView* v3 = new TestView(); + v3->SetBounds(10, 10, 60, 60); + v2->AddChildView(v3); + + TestView* v4 = new TestView(); + v4->SetBounds(10, 200, 100, 100); + v1->AddChildView(v4); + + // Make sure to paint current rects + PaintRootView(root, empty_paint); + + + v1->Reset(); + v2->Reset(); + v3->Reset(); + v4->Reset(); + v3->SchedulePaint(10, 10, 10, 10); + PaintRootView(root, empty_paint); + + SkRect tmp_rect; + + tmp_rect.set(SkIntToScalar(10), + SkIntToScalar(10), + SkIntToScalar(20), + SkIntToScalar(20)); + CheckRect(v3->last_clip_, tmp_rect); + + tmp_rect.set(SkIntToScalar(20), + SkIntToScalar(20), + SkIntToScalar(30), + SkIntToScalar(30)); + CheckRect(v2->last_clip_, tmp_rect); + + tmp_rect.set(SkIntToScalar(30), + SkIntToScalar(30), + SkIntToScalar(40), + SkIntToScalar(40)); + CheckRect(v1->last_clip_, tmp_rect); + + // Make sure v4 was not painted + tmp_rect.setEmpty(); + CheckRect(v4->last_clip_, tmp_rect); + + window.DestroyWindow(); +} +*/ +typedef std::vector<View*> ViewList; + +class RemoveViewObserver : public NotificationObserver { +public: + RemoveViewObserver() { } + + void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + ASSERT_TRUE(type == NOTIFY_VIEW_REMOVED); + removed_views_.push_back(Source<ChromeViews::View>(source).ptr()); + } + + bool WasRemoved(ChromeViews::View* view) { + return std::find(removed_views_.begin(), removed_views_.end(), view) != + removed_views_.end(); + } + + ViewList removed_views_; + +}; + +TEST_F(ViewTest, RemoveNotification) { + scoped_ptr<RemoveViewObserver> observer(new RemoveViewObserver); + + NotificationService::current()->AddObserver( + observer.get(), NOTIFY_VIEW_REMOVED, NotificationService::AllSources()); + + ChromeViews::Window* window = new ChromeViews::Window; + ChromeViews::RootView* root_view = window->GetRootView(); + + View* v1 = new View; + root_view->AddChildView(v1); + View* v11 = new View; + v1->AddChildView(v11); + View* v111 = new View; + v11->AddChildView(v111); + View* v112 = new View; + v11->AddChildView(v112); + View* v113 = new View; + v11->AddChildView(v113); + View* v1131 = new View; + v113->AddChildView(v1131); + View* v12 = new View; + v1->AddChildView(v12); + + View* v2 = new View; + root_view->AddChildView(v2); + View* v21 = new View; + v2->AddChildView(v21); + View* v211 = new View; + v21->AddChildView(v211); + + // Try removing a leaf view. + v21->RemoveChildView(v211); + EXPECT_EQ(1, observer->removed_views_.size()); + EXPECT_TRUE(observer->WasRemoved(v211)); + delete v211; // We won't use this one anymore. + + // Now try removing a view with a hierarchy of depth 1. + observer->removed_views_.clear(); + v11->RemoveChildView(v113); + EXPECT_EQ(observer->removed_views_.size(), 2); + EXPECT_TRUE(observer->WasRemoved(v113) && observer->WasRemoved(v1131)); + delete v113; // We won't use this one anymore. + + // Now remove even more. + observer->removed_views_.clear(); + root_view->RemoveChildView(v1); + EXPECT_EQ(observer->removed_views_.size(), 5); + EXPECT_TRUE(observer->WasRemoved(v1) && + observer->WasRemoved(v11) && observer->WasRemoved(v12) && + observer->WasRemoved(v111) && observer->WasRemoved(v112)); + + // Put v1 back for more tests. + root_view->AddChildView(v1); + observer->removed_views_.clear(); + + // Now delete the root view (deleting the window will trigger a delete of the + // RootView) and make sure we are notified that the views were removed. + delete window; + EXPECT_EQ(observer->removed_views_.size(), 7); + EXPECT_TRUE(observer->WasRemoved(v1) && observer->WasRemoved(v2) && + observer->WasRemoved(v11) && observer->WasRemoved(v12) && + observer->WasRemoved(v21) && + observer->WasRemoved(v111) && observer->WasRemoved(v112)); + + NotificationService::current()->RemoveObserver(observer.get(), + NOTIFY_VIEW_REMOVED, NotificationService::AllSources()); +} diff --git a/chrome/views/views.vcproj b/chrome/views/views.vcproj new file mode 100644 index 0000000..0d35a46 --- /dev/null +++ b/chrome/views/views.vcproj @@ -0,0 +1,611 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="views" + ProjectGUID="{6F9258E5-294F-47B2-919D-17FFE7A8B751}" + RootNamespace="Views" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets=".\views.vsprops;$(SolutionDir)..\build\debug.vsprops;..\tools\build\win\precompiled_wtl.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets=".\views.vsprops;$(SolutionDir)..\build\release.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Accessibility" + > + <File + RelativePath=".\accessibility\accessible_wrapper.cc" + > + </File> + <File + RelativePath=".\accessibility\accessible_wrapper.h" + > + </File> + <File + RelativePath=".\accessibility\autocomplete_accessibility.cc" + > + </File> + <File + RelativePath=".\accessibility\autocomplete_accessibility.h" + > + </File> + <File + RelativePath=".\accessibility\view_accessibility.cc" + > + </File> + <File + RelativePath=".\accessibility\view_accessibility.h" + > + </File> + </Filter> + <File + RelativePath=".\accelerator.cc" + > + </File> + <File + RelativePath=".\accelerator.h" + > + </File> + <File + RelativePath=".\accelerator_handler.cc" + > + </File> + <File + RelativePath=".\accelerator_handler.h" + > + </File> + <File + RelativePath=".\aero_tooltip_manager.cc" + > + </File> + <File + RelativePath=".\aero_tooltip_manager.h" + > + </File> + <File + RelativePath=".\app_modal_dialog_delegate.h" + > + </File> + <File + RelativePath=".\background.cc" + > + </File> + <File + RelativePath=".\background.h" + > + </File> + <File + RelativePath=".\base_button.cc" + > + </File> + <File + RelativePath=".\base_button.h" + > + </File> + <File + RelativePath=".\bitmap_scroll_bar.cc" + > + </File> + <File + RelativePath=".\bitmap_scroll_bar.h" + > + </File> + <File + RelativePath=".\border.cc" + > + </File> + <File + RelativePath=".\border.h" + > + </File> + <File + RelativePath=".\button.cc" + > + </File> + <File + RelativePath=".\button.h" + > + </File> + <File + RelativePath=".\button_dropdown.cc" + > + </File> + <File + RelativePath=".\button_dropdown.h" + > + </File> + <File + RelativePath=".\checkbox.cc" + > + </File> + <File + RelativePath=".\checkbox.h" + > + </File> + <File + RelativePath=".\chrome_menu.cc" + > + </File> + <File + RelativePath=".\chrome_menu.h" + > + </File> + <File + RelativePath=".\client_view.cc" + > + </File> + <File + RelativePath=".\client_view.h" + > + </File> + <File + RelativePath=".\combo_box.cc" + > + </File> + <File + RelativePath=".\combo_box.h" + > + </File> + <File + RelativePath=".\controller.h" + > + </File> + <File + RelativePath=".\custom_frame_window.cc" + > + </File> + <File + RelativePath=".\custom_frame_window.h" + > + </File> + <File + RelativePath=".\decision.cc" + > + </File> + <File + RelativePath=".\decision.h" + > + </File> + <File + RelativePath=".\dialog_delegate.h" + > + </File> + <File + RelativePath=".\event.cc" + > + </File> + <File + RelativePath=".\event.h" + > + </File> + <File + RelativePath=".\external_focus_tracker.cc" + > + </File> + <File + RelativePath=".\external_focus_tracker.h" + > + </File> + <File + RelativePath=".\focus_manager.cc" + > + </File> + <File + RelativePath=".\focus_manager.h" + > + </File> + <File + RelativePath=".\grid_layout.cc" + > + </File> + <File + RelativePath=".\grid_layout.h" + > + </File> + <File + RelativePath=".\group_table_view.cc" + > + </File> + <File + RelativePath=".\group_table_view.h" + > + </File> + <File + RelativePath=".\hwnd_notification_source.h" + > + </File> + <File + RelativePath=".\hwnd_view.cc" + > + </File> + <File + RelativePath=".\hwnd_view.h" + > + </File> + <File + RelativePath=".\hwnd_view_container.cc" + > + </File> + <File + RelativePath=".\hwnd_view_container.h" + > + </File> + <File + RelativePath=".\image_view.cc" + > + </File> + <File + RelativePath=".\image_view.h" + > + </File> + <File + RelativePath=".\label.cc" + > + </File> + <File + RelativePath=".\label.h" + > + </File> + <File + RelativePath=".\layout_manager.cc" + > + </File> + <File + RelativePath=".\layout_manager.h" + > + </File> + <File + RelativePath=".\link.cc" + > + </File> + <File + RelativePath=".\link.h" + > + </File> + <File + RelativePath=".\menu.cc" + > + </File> + <File + RelativePath=".\menu.h" + > + </File> + <File + RelativePath=".\menu_button.cc" + > + </File> + <File + RelativePath=".\menu_button.h" + > + </File> + <File + RelativePath=".\message_box_view.cc" + > + </File> + <File + RelativePath=".\message_box_view.h" + > + </File> + <File + RelativePath=".\native_button.cc" + > + </File> + <File + RelativePath=".\native_button.h" + > + </File> + <File + RelativePath=".\native_control.cc" + > + </File> + <File + RelativePath=".\native_control.h" + > + </File> + <File + RelativePath=".\native_scroll_bar.cc" + > + </File> + <File + RelativePath=".\native_scroll_bar.h" + > + </File> + <File + RelativePath=".\painter.cc" + > + </File> + <File + RelativePath=".\painter.h" + > + </File> + <File + RelativePath="..\tools\build\win\precompiled_wtl.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + </File> + <File + RelativePath="..\tools\build\win\precompiled_wtl.h" + > + </File> + <File + RelativePath=".\radio_button.cc" + > + </File> + <File + RelativePath=".\radio_button.h" + > + </File> + <File + RelativePath=".\repeat_controller.cc" + > + </File> + <File + RelativePath=".\repeat_controller.h" + > + </File> + <File + RelativePath=".\resize_corner.cc" + > + </File> + <File + RelativePath=".\resize_corner.h" + > + </File> + <File + RelativePath=".\root_view.cc" + > + </File> + <File + RelativePath=".\root_view.h" + > + </File> + <File + RelativePath=".\root_view_drop_target.cc" + > + </File> + <File + RelativePath=".\root_view_drop_target.h" + > + </File> + <File + RelativePath=".\scroll_bar.cc" + > + </File> + <File + RelativePath=".\scroll_bar.h" + > + </File> + <File + RelativePath=".\scroll_view.cc" + > + </File> + <File + RelativePath=".\scroll_view.h" + > + </File> + <File + RelativePath=".\separator.cc" + > + </File> + <File + RelativePath=".\separator.h" + > + </File> + <File + RelativePath=".\tabbed_pane.cc" + > + </File> + <File + RelativePath=".\tabbed_pane.h" + > + </File> + <File + RelativePath=".\table_view.cc" + > + </File> + <File + RelativePath=".\table_view.h" + > + </File> + <File + RelativePath=".\text_button.cc" + > + </File> + <File + RelativePath=".\text_button.h" + > + </File> + <File + RelativePath=".\text_field.cc" + > + </File> + <File + RelativePath=".\text_field.h" + > + </File> + <File + RelativePath=".\throbber.cc" + > + </File> + <File + RelativePath=".\throbber.h" + > + </File> + <File + RelativePath=".\tooltip_manager.cc" + > + </File> + <File + RelativePath=".\tooltip_manager.h" + > + </File> + <File + RelativePath=".\tree_node_model.h" + > + </File> + <File + RelativePath=".\tree_view.cc" + > + </File> + <File + RelativePath=".\tree_view.h" + > + </File> + <File + RelativePath=".\view.cc" + > + </File> + <File + RelativePath=".\view.h" + > + </File> + <File + RelativePath=".\view_container.h" + > + </File> + <File + RelativePath=".\view_menu_delegate.h" + > + </File> + <File + RelativePath=".\view_storage.cc" + > + </File> + <File + RelativePath=".\view_storage.h" + > + </File> + <File + RelativePath=".\window.cc" + > + </File> + <File + RelativePath=".\window.h" + > + </File> + <File + RelativePath=".\window_delegate.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/chrome/views/views.vsprops b/chrome/views/views.vsprops new file mode 100644 index 0000000..8f1625a --- /dev/null +++ b/chrome/views/views.vsprops @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="views" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;..\tools\build\win\using_generated_strings.vsprops" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\;"$(IntDir)\..\generated_resources\"" + /> +</VisualStudioPropertySheet> diff --git a/chrome/views/window.cc b/chrome/views/window.cc new file mode 100644 index 0000000..24b4925 --- /dev/null +++ b/chrome/views/window.cc @@ -0,0 +1,660 @@ +// 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 "chrome/views/window.h" + +#include "chrome/app/chrome_dll_resource.h" +// TODO(beng): some day make this unfortunate dependency not exist. +#include "chrome/browser/browser_list.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/client_view.h" +#include "chrome/views/custom_frame_window.h" +#include "chrome/views/window_delegate.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +// static +HCURSOR Window::nwse_cursor_ = NULL; + +// If the hung renderer warning doesn't fit on screen, the amount of padding to +// be left between the edge of the window and the edge of the nearest monitor, +// after the window is nudged back on screen. Pixels. +static const int kMonitorEdgePadding = 10; + +//////////////////////////////////////////////////////////////////////////////// +// Window, public: + +Window::Window() + : HWNDViewContainer(), + focus_on_creation_(true), + client_view_(NULL), + window_delegate_(NULL), + owning_hwnd_(NULL), + minimum_size_(100, 100), + is_modal_(false), + restored_enabled_(false), + is_always_on_top_(false), + use_client_view_(true), + accepted_(false), + window_closed_(false) { + InitClass(); + // Initialize these values to 0 so that subclasses can override the default + // behavior before calling Init. + set_window_style(0); + set_window_ex_style(0); + BrowserList::AddDependentWindow(this); +} + +Window::~Window() { + BrowserList::RemoveDependentWindow(this); +} + +// static +Window* Window::CreateChromeWindow(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate) { + if (win_util::ShouldUseVistaFrame()) { + Window* window = new Window; + window->Init(parent, bounds, contents_view, window_delegate); + return window; + } + CustomFrameWindow* window = new CustomFrameWindow; + window->Init(parent, bounds, contents_view, window_delegate); + return window; +} + +void Window::Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate) { + window_delegate_ = window_delegate; + // We need to save the parent window, since later calls to GetParent() will + // return NULL. + owning_hwnd_ = parent; + // We call this after initializing our members since our implementations of + // assorted HWNDViewContainer functions may be called during initialization. + if (window_delegate_) { + is_modal_ = window_delegate_->IsModal(); + if (is_modal_) + BecomeModal(); + is_always_on_top_ = window_delegate_->IsAlwaysOnTop(); + } + + if (window_style() == 0) + set_window_style(CalculateWindowStyle()); + if (window_ex_style() == 0) + set_window_ex_style(CalculateWindowExStyle()); + + // A child window never owns its own focus manager, it uses the one + // associated with the root of the window tree... + if (use_client_view_) { + client_view_ = new ClientView(this, contents_view); + // A Window almost always owns its own focus manager, even if it's a child + // window. File a bug if you find a circumstance where this isn't the case + // and we can adjust this API. Note that if this is not the case, you'll + // also have to change SetInitialFocus() as it relies on the window's focus + // manager. + HWNDViewContainer::Init(parent, bounds, client_view_, true); + } else { + HWNDViewContainer::Init(parent, bounds, contents_view, true); + } + + if (window_delegate_) { + std::wstring window_title = window_delegate_->GetWindowTitle(); + SetWindowText(GetHWND(), window_title.c_str()); + } + + win_util::SetWindowUserData(GetHWND(), this); + + // Restore the window's placement from the controller. + CRect saved_bounds(0, 0, 0, 0); + bool maximized = false; + if (window_delegate_ && + window_delegate_->RestoreWindowPosition(&saved_bounds, + &maximized, + &is_always_on_top_)) { + // Make sure the bounds are at least the minimum size. + if (saved_bounds.Width() < minimum_size_.cx) { + saved_bounds.SetRect(saved_bounds.left, saved_bounds.top, + saved_bounds.right + minimum_size_.cx - + saved_bounds.Width(), + saved_bounds.bottom); + } + + if (saved_bounds.Height() < minimum_size_.cy) { + saved_bounds.SetRect(saved_bounds.left, saved_bounds.top, + saved_bounds.right, + saved_bounds.bottom + minimum_size_.cy - + saved_bounds.Height()); + } + + WINDOWPLACEMENT placement = {0}; + placement.length = sizeof(WINDOWPLACEMENT); + placement.rcNormalPosition = saved_bounds; + if (maximized) + placement.showCmd = SW_SHOWMAXIMIZED; + ::SetWindowPlacement(GetHWND(), &placement); + + if (is_always_on_top_ != window_delegate_->IsAlwaysOnTop()) + AlwaysOnTopChanged(); + } else if (bounds.IsEmpty()) { + // Size the window to the content and center over the parent. + SizeWindowToDefault(); + } + + if (window_delegate_ && window_delegate->HasAlwaysOnTopMenu()) + AddAlwaysOnTopSystemMenuItem(); +} + +gfx::Size Window::CalculateWindowSizeForClientSize( + const gfx::Size& client_size) const { + RECT r = { 0, 0, client_size.width(), client_size.height() }; + ::AdjustWindowRectEx(&r, window_style(), FALSE, window_ex_style()); + return gfx::Size(r.right - r.left, r.bottom - r.top); +} + +gfx::Size Window::CalculateMaximumSize() const { + // If this is a top level window, the maximum size is the size of the working + // rect of the display the window is on, less padding. If this is a child + // (constrained) window, the maximum size of this Window are the bounds of the + // parent window, less padding. + DCHECK(GetHWND()) << "Cannot calculate maximum size before Init() is called"; + gfx::Rect working_rect; + HWND parent_hwnd = ::GetParent(GetHWND()); + if (parent_hwnd) { + RECT parent_rect; + ::GetClientRect(parent_hwnd, &parent_rect); + working_rect = parent_rect; + } else { + HMONITOR current_monitor = + ::MonitorFromWindow(GetHWND(), MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + ::GetMonitorInfo(current_monitor, &mi); + working_rect = mi.rcWork; + } + working_rect.Inset(kMonitorEdgePadding, kMonitorEdgePadding); + return working_rect.size(); +} + +void Window::Show() { + ShowWindow(SW_SHOW); + SetInitialFocus(); +} + +void Window::Activate() { + ::SetWindowPos(GetHWND(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); +} + +void Window::SetBounds(const gfx::Rect& bounds) { + SetBounds(bounds, NULL); +} + +void Window::SetBounds(const gfx::Rect& bounds, HWND other_hwnd) { + win_util::SetChildBounds(GetHWND(), GetParent(), other_hwnd, bounds, + kMonitorEdgePadding, 0); +} + +void Window::Close() { + if (window_closed_) { + // It appears we can hit this code path if you close a modal dialog then + // close the last browser before the destructor is hit, which triggers + // invoking Close again. I'm short circuiting this code path to avoid + // calling into the delegate twice, which is problematic. + return; + } + + bool can_close = true; + // Ask the delegate if we're allowed to close. The user may not have left the + // window in a state where this is allowable (e.g. unsaved work). Also, don't + // call Cancel on the delegate if we've already been accepted and are in the + // process of being closed. Furthermore, if we have only an OK button, but no + // Cancel button, and we're closing without being accepted, call Accept to + // see if we should close. + if (!accepted_ && window_delegate_) { + DialogDelegate* dd = window_delegate_->AsDialogDelegate(); + if (dd) { + int buttons = dd->GetDialogButtons(); + if (buttons & DialogDelegate::DIALOGBUTTON_CANCEL) + can_close = dd->Cancel(); + else if (buttons & DialogDelegate::DIALOGBUTTON_OK) + can_close = dd->Accept(true); + } + } + if (can_close) { + SaveWindowPosition(); + RestoreEnabledIfNecessary(); + HWNDViewContainer::Close(); + // If the user activates another app after opening us, then comes back and + // closes us, we want our owner to gain activation. But only if the owner + // is visible. If we don't manually force that here, the other app will + // regain activation instead. + if (owning_hwnd_ && GetHWND() == GetForegroundWindow() && + IsWindowVisible(owning_hwnd_)) { + SetForegroundWindow(owning_hwnd_); + } + window_closed_ = true; + } +} + +bool Window::IsMaximized() const { + return !!::IsZoomed(GetHWND()); +} + +bool Window::IsMinimized() const { + return !!::IsIconic(GetHWND()); +} + +void Window::EnableClose(bool enable) { + EnableMenuItem(::GetSystemMenu(GetHWND(), false), + SC_CLOSE, + enable ? MF_ENABLED : MF_GRAYED); + + // Let the window know the frame changed. + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_FRAMECHANGED | + SWP_NOACTIVATE | + SWP_NOCOPYBITS | + SWP_NOMOVE | + SWP_NOOWNERZORDER | + SWP_NOREPOSITION | + SWP_NOSENDCHANGING | + SWP_NOSIZE | + SWP_NOZORDER); +} + +void Window::UpdateDialogButtons() { + if (client_view_) + client_view_->UpdateDialogButtons(); +} + +void Window::AcceptWindow() { + accepted_ = true; + if (window_delegate_) { + DialogDelegate* dd = window_delegate_->AsDialogDelegate(); + if (dd) + accepted_ = dd->Accept(false); + } + if (accepted_) + Close(); +} + +void Window::CancelWindow() { + // Call the standard Close handler, which checks with the delegate before + // proceeding. This checking _isn't_ done here, but in the WM_CLOSE handler, + // so that the close box on the window also shares this code path. + Close(); +} + +void Window::UpdateWindowTitle() { + if (window_delegate_) { + std::wstring window_title = window_delegate_->GetWindowTitle(); + SetWindowText(GetHWND(), window_title.c_str()); + } +} + +// static +bool Window::SaveWindowPositionToPrefService(PrefService* pref_service, + const std::wstring& entry, + const CRect& bounds, + bool maximized, + bool always_on_top) { + DCHECK(pref_service); + DictionaryValue* win_pref = pref_service->GetMutableDictionary(entry.c_str()); + DCHECK(win_pref); + + win_pref->SetInteger(L"left", bounds.left); + win_pref->SetInteger(L"top", bounds.top); + win_pref->SetInteger(L"right", bounds.right); + win_pref->SetInteger(L"bottom", bounds.bottom); + win_pref->SetBoolean(L"maximized", maximized); + win_pref->SetBoolean(L"always_on_top", always_on_top); + return true; +} + +// static +bool Window::RestoreWindowPositionFromPrefService(PrefService* pref_service, + const std::wstring& entry, + CRect* bounds, + bool* maximized, + bool* always_on_top) { + DCHECK(pref_service); + DCHECK(bounds); + DCHECK(maximized); + DCHECK(always_on_top); + + const DictionaryValue* dictionary = pref_service->GetDictionary(entry.c_str()); + if (!dictionary) + return false; + + int left, top, right, bottom; + bool temp_maximized, temp_always_on_top; + if (!dictionary || !dictionary->GetInteger(L"left", &left) || + !dictionary->GetInteger(L"top", &top) || + !dictionary->GetInteger(L"right", &right) || + !dictionary->GetInteger(L"bottom", &bottom) || + !dictionary->GetBoolean(L"maximized", &temp_maximized) || + !dictionary->GetBoolean(L"always_on_top", &temp_always_on_top)) + return false; + + bounds->SetRect(left, top, right, bottom); + *maximized = temp_maximized; + *always_on_top = temp_always_on_top; + return true; +} + +// static +gfx::Size Window::GetLocalizedContentsSize(int col_resource_id, + int row_resource_id) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + + double chars = _wtof(l10n_util::GetString(col_resource_id).c_str()); + double lines = _wtof(l10n_util::GetString(row_resource_id).c_str()); + + int width = static_cast<int>(font.ave_char_width() * chars); + int height = static_cast<int>(font.height() * lines); + + DCHECK(width > 0 && height > 0); + + return gfx::Size(width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, protected: + +void Window::SizeWindowToDefault() { + if (client_view_) { + CSize pref(0, 0); + client_view_->GetPreferredSize(&pref); + DCHECK(pref.cx > 0 && pref.cy > 0); + // CenterAndSizeWindow adjusts the window size to accommodate the non-client + // area. + win_util::CenterAndSizeWindow(owning_window(), GetHWND(), pref, true); + } +} + +void Window::SetInitialFocus() { + if (!focus_on_creation_) + return; + + bool focus_set = false; + if (window_delegate_) { + ChromeViews::View* v = window_delegate_->GetInitiallyFocusedView(); + // For dialogs, try to focus either the OK or Cancel buttons if any. + if (!v && window_delegate_->AsDialogDelegate() && client_view_) { + if (client_view_->ok_button()) + v = client_view_->ok_button(); + else if (client_view_->cancel_button()) + v = client_view_->cancel_button(); + } + if (v) { + focus_set = true; + // In order to make that view the initially focused one, we make it the + // focused view on the focus manager and we store the focused view. + // When the window is activated, the focus manager will restore the + // stored focused view. + FocusManager* focus_manager = FocusManager::GetFocusManager(GetHWND()); + DCHECK(focus_manager); + focus_manager->SetFocusedView(v); + focus_manager->StoreFocusedView(); + } + } + + if (!focus_set && focus_on_creation_) { + // The window does not get keyboard messages unless we focus it, not sure + // why. + SetFocus(GetHWND()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, HWNDViewContainer overrides: + +void Window::OnActivate(UINT action, BOOL minimized, HWND window) { + if (action == WA_INACTIVE) + SaveWindowPosition(); +} + +void Window::OnCommand(UINT notification_code, int command_id, HWND window) { + if (window_delegate_) + window_delegate_->ExecuteWindowsCommand(command_id); +} + +void Window::OnDestroy() { + if (window_delegate_) { + window_delegate_->WindowClosing(); + window_delegate_ = NULL; + } + RestoreEnabledIfNecessary(); + HWNDViewContainer::OnDestroy(); +} + +LRESULT Window::OnEraseBkgnd(HDC dc) { + SetMsgHandled(TRUE); + return 1; +} + +LRESULT Window::OnNCHitTest(const CPoint& point) { + // We paint the size box over the content area sometimes... check to see if + // the mouse is over it... + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); + if (client_view_ && client_view_->PointIsInSizeBox(gfx::Point(temp))) + return HTBOTTOMRIGHT; + + // Otherwise, we let Windows do all the native frame non-client handling for + // us. + SetMsgHandled(FALSE); + return 0; +} + +LRESULT Window::OnSetCursor(HWND window, UINT hittest_code, UINT message) { + if (hittest_code == HTBOTTOMRIGHT) { + // If the mouse was over the resize gripper, make sure the right cursor is + // supplied... + SetCursor(nwse_cursor_); + return TRUE; + } + // Otherwise just let Windows do the rest. + SetMsgHandled(FALSE); + return TRUE; +} + +void Window::OnSize(UINT size_param, const CSize& new_size) { + if (root_view_->GetWidth() == new_size.cx && + root_view_->GetHeight() == new_size.cy) + return; + + SaveWindowPosition(); + ChangeSize(size_param, new_size); + RedrawWindow(GetHWND(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); +} + +void Window::OnSysCommand(UINT notification_code, CPoint click) { + if (notification_code == IDC_ALWAYS_ON_TOP) { + is_always_on_top_ = !is_always_on_top_; + + // Change the menu check state. + HMENU system_menu = ::GetSystemMenu(GetHWND(), FALSE); + MENUITEMINFO menu_info; + memset(&menu_info, 0, sizeof(MENUITEMINFO)); + menu_info.cbSize = sizeof(MENUITEMINFO); + BOOL r = ::GetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP, + FALSE, &menu_info); + DCHECK(r); + menu_info.fMask = MIIM_STATE; + if (is_always_on_top_) + menu_info.fState = MFS_CHECKED; + r = ::SetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP, FALSE, &menu_info); + + // Now change the actual window's behavior. + AlwaysOnTopChanged(); + } else { + // Use the default implementation for any other command. + DefWindowProc(GetHWND(), WM_SYSCOMMAND, notification_code, + MAKELPARAM(click.y, click.x)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, private: + +void Window::BecomeModal() { + // We implement modality by crawling up the hierarchy of windows starting + // at the owner, disabling all of them so that they don't receive input + // messages. + DCHECK(owning_hwnd_) << "Can't create a modal dialog without an owner"; + HWND start = owning_hwnd_; + while (start != NULL) { + ::EnableWindow(start, FALSE); + start = ::GetParent(start); + } +} + +void Window::AddAlwaysOnTopSystemMenuItem() { + // The Win32 API requires that we own the text. + always_on_top_menu_text_ = l10n_util::GetString(IDS_ALWAYS_ON_TOP); + + // Let's insert a menu to the window. + HMENU system_menu = ::GetSystemMenu(GetHWND(), FALSE); + int index = ::GetMenuItemCount(system_menu) - 1; + if (index < 0) { + // Paranoia check. + NOTREACHED(); + index = 0; + } + // First we add the separator. + MENUITEMINFO menu_info; + memset(&menu_info, 0, sizeof(MENUITEMINFO)); + menu_info.cbSize = sizeof(MENUITEMINFO); + menu_info.fMask = MIIM_FTYPE; + menu_info.fType = MFT_SEPARATOR; + ::InsertMenuItem(system_menu, index, TRUE, &menu_info); + + // Then the actual menu. + menu_info.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE; + menu_info.fType = MFT_STRING; + menu_info.fState = MFS_ENABLED; + if (is_always_on_top_) + menu_info.fState |= MFS_CHECKED; + menu_info.wID = IDC_ALWAYS_ON_TOP; + menu_info.dwTypeData = const_cast<wchar_t*>(always_on_top_menu_text_.c_str()); + ::InsertMenuItem(system_menu, index, TRUE, &menu_info); +} + +void Window::RestoreEnabledIfNecessary() { + if (is_modal_ && !restored_enabled_) { + restored_enabled_ = true; + // If we were run modally, we need to undo the disabled-ness we inflicted on + // the owner's parent hierarchy. + HWND start = owning_hwnd_; + while (start != NULL) { + ::EnableWindow(start, TRUE); + start = ::GetParent(start); + } + } +} + +void Window::AlwaysOnTopChanged() { + ::SetWindowPos(GetHWND(), + is_always_on_top_ ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); +} + +DWORD Window::CalculateWindowStyle() { + if (window_delegate_) { + DWORD window_styles = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_SYSMENU; + bool can_resize = window_delegate_->CanResize(); + bool can_maximize = window_delegate_->CanMaximize(); + if ((can_resize && can_maximize) || can_maximize) { + window_styles |= WS_OVERLAPPEDWINDOW; + } else if (can_resize) { + window_styles |= WS_OVERLAPPED | WS_THICKFRAME; + } + if (window_delegate_->AsDialogDelegate()) { + window_styles |= DS_MODALFRAME; + // NOTE: Turning this off means we lose the close button, which is bad. + // Turning it on though means the user can maximize or size the window + // from the system menu, which is worse. We may need to provide our own + // menu to get the close button to appear properly. + // window_styles &= ~WS_SYSMENU; + } + return window_styles; + } + return window_style(); +} + +DWORD Window::CalculateWindowExStyle() { + if (window_delegate_) { + DWORD window_ex_styles = 0; + if (window_delegate_->AsDialogDelegate()) { + window_ex_styles |= WS_EX_DLGMODALFRAME; + } else if (!(window_style() & WS_CHILD)) { + window_ex_styles |= WS_EX_APPWINDOW; + } + if (window_delegate_->IsAlwaysOnTop()) + window_ex_styles |= WS_EX_TOPMOST; + return window_ex_styles; + } + return window_ex_style(); +} + +void Window::SaveWindowPosition() { + if (window_delegate_) { + WINDOWPLACEMENT win_placement = { 0 }; + win_placement.length = sizeof(WINDOWPLACEMENT); + + BOOL r = GetWindowPlacement(GetHWND(), &win_placement); + DCHECK(r); + + bool maximized = (win_placement.showCmd == SW_SHOWMAXIMIZED); + CRect window_bounds(win_placement.rcNormalPosition); + window_delegate_->SaveWindowPosition(window_bounds, + maximized, + is_always_on_top_); + } +} + +void Window::InitClass() { + static bool initialized = false; + if (!initialized) { + nwse_cursor_ = LoadCursor(NULL, IDC_SIZENWSE); + initialized = true; + } +} + +} // namespace ChromeViews diff --git a/chrome/views/window.h b/chrome/views/window.h new file mode 100644 index 0000000..8e30ad9 --- /dev/null +++ b/chrome/views/window.h @@ -0,0 +1,275 @@ +// 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. + +#ifndef CHROME_VIEWS_WINDOW_H__ +#define CHROME_VIEWS_WINDOW_H__ + +#include "chrome/views/hwnd_view_container.h" + +namespace gfx { +class Size; +class Path; +class Point; +}; + +class PrefService; + +namespace ChromeViews { + +class ClientView; +class Client; +class WindowDelegate; + +//////////////////////////////////////////////////////////////////////////////// +// +// Window +// +// A Window is a HWNDViewContainer that has a caption and a border. The frame +// is rendered by the operating system. +// +//////////////////////////////////////////////////////////////////////////////// +class Window : public HWNDViewContainer { + public: + // TODO(beng): (Cleanup) move these into private section, effectively making + // this class "final" to all but designated friends within + // ChromeViews. Users in browser/ should always construct with + // CreateChromeWindow which will give the right version, + // depending on platform & configuration. + Window(); + virtual ~Window(); + + // Creates the appropriate Window class for a Chrome dialog or window. This + // means a ChromeWindow or a standard Windows frame. + static Window* CreateChromeWindow(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate); + + // Create the Window. + // If parent is NULL, this Window is top level on the desktop. + // |contents_view| is a ChromeView that will be displayed in the client area + // of the Window, as the sole child view of the RootView. + // |window_delegate| is an object implementing WindowDelegate that can perform + // controller-like tasks for this window, such as obtaining its preferred + // placement and state from preferences (which override the default position + // and size specified in |bounds|) and executing commands. Can be NULL. + // If |bounds| is empty, the view is queried for its preferred size and + // centered on screen. + void Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + WindowDelegate* window_delegate); + + // Return the size of window (including non-client area) required to contain + // a window of the specified client size. + virtual gfx::Size CalculateWindowSizeForClientSize( + const gfx::Size& client_size) const; + + // Return the maximum size possible size the window should be have if it is + // to be positioned within the bounds of the current "work area" (screen or + // parent window). + gfx::Size CalculateMaximumSize() const; + + // Show the window. + virtual void Show(); + + // Activate the window, assuming it already exists and is visible. + void Activate(); + + // Sizes and/or places the window to the specified bounds, size or position. + void SetBounds(const gfx::Rect& bounds); + // As above, except the window is inserted after |other_hwnd| in the window + // Z-order. If this window's HWND is not yet visible, other_hwnd's monitor + // is used as the constraining rectangle, rather than this window's hwnd's + // monitor. + void SetBounds(const gfx::Rect& bounds, HWND other_hwnd); + + // Closes the window, ultimately destroying it. + virtual void Close(); + + // Whether or not the window is maximized or minimized. + bool IsMaximized() const; + bool IsMinimized() const; + + // Toggles the enable state for the Close button (and the Close menu item in + // the system menu). + virtual void EnableClose(bool enable); + + WindowDelegate* window_delegate() const { return window_delegate_; } + void set_window_delegate(WindowDelegate* delegate) { + window_delegate_ = delegate; + } + + // Set whether or not we should insert a client view. See comment below. + void set_use_client_view(bool use_client_view) { + use_client_view_ = use_client_view; + } + + void set_focus_on_creation(bool focus_on_creation) { + focus_on_creation_ = focus_on_creation; + } + + // Updates the enabled state and label of the dialog buttons visible in this + // window. + void UpdateDialogButtons(); + + // Called when the window should be canceled or accepted, if it is a dialog + // box. + void AcceptWindow(); + void CancelWindow(); + + // Tell the window to update its title from the delegate. + virtual void UpdateWindowTitle(); + + // The parent of this window. + HWND owning_window() const { + return owning_hwnd_; + } + + // Convenience methods for storing/retrieving window location information + // to/from a PrefService using the specified |entry| name. + // WindowDelegate instances can use these methods in their implementation of + // SaveWindowPosition/RestoreWindowPosition to save windows' location to + // preferences. + static bool SaveWindowPositionToPrefService(PrefService* pref_service, + const std::wstring& entry, + const CRect& bounds, + bool maximized, + bool always_on_top); + // Returns true if the window location was retrieved from the PrefService and + // set in |bounds|, |maximized| and |always_on_top|. + static bool RestoreWindowPositionFromPrefService(PrefService* pref_service, + const std::wstring& entry, + CRect* bounds, + bool* maximized, + bool* always_on_top); + + // Returns the preferred size of the contents view of this window based on + // its localized size data. The width in cols is held in a localized string + // resource identified by |col_resource_id|, the height in the same fashion. + // TODO(beng): This should eventually live somewhere else, probably closer to + // ClientView. + static gfx::Size GetLocalizedContentsSize(int col_resource_id, + int row_resource_id); + + protected: + virtual void SizeWindowToDefault(); + + // Sets-up the focus manager with the view that should have focus when the + // window is shown the first time. If NULL is returned, the focus goes to the + // button if there is one, otherwise the to the Cancel button. + void SetInitialFocus(); + + // Overridden from HWNDViewContainer: + virtual void OnActivate(UINT action, BOOL minimized, HWND window); + virtual void OnCommand(UINT notification_code, int command_id, HWND window); + virtual void OnDestroy(); + virtual LRESULT OnEraseBkgnd(HDC dc); + virtual LRESULT OnNCHitTest(const CPoint& point); + virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message); + virtual void OnSize(UINT size_param, const CSize& new_size); + virtual void OnSysCommand(UINT notification_code, CPoint click); + + // The client view object that contains the client area of the window, + // including optional dialog buttons. + ClientView* client_view_; + + // Our window delegate (see Init method for documentation). + WindowDelegate* window_delegate_; + + private: + // Set the window as modal (by disabling all the other windows). + void BecomeModal(); + + // Add an item for "Always on Top" to the System Menu. + void AddAlwaysOnTopSystemMenuItem(); + + // If necessary, enables all ancestors. + void RestoreEnabledIfNecessary(); + + // Update the window style to reflect the always on top state. + void AlwaysOnTopChanged(); + + // Calculate the appropriate window styles for this window. + DWORD CalculateWindowStyle(); + DWORD CalculateWindowExStyle(); + + // Asks the delegate if any to save the window's location and size. + void SaveWindowPosition(); + + // Static resource initialization. + static void InitClass(); + static HCURSOR nwse_cursor_; + + // Whether we should SetFocus() on a newly created window after + // Init(). Defaults to true. + bool focus_on_creation_; + + // We need to save the parent window that spawned us, since GetParent() + // returns NULL for dialogs. + HWND owning_hwnd_; + + // The smallest size the window can be. + CSize minimum_size_; + + // Whether or not the window is modal. This comes from the delegate and is + // cached at Init time to avoid calling back to the delegate from the + // destructor. + bool is_modal_; + + // Whether all ancestors have been enabled. This is only used if is_modal_ is + // true. + bool restored_enabled_; + + // Whether the window is currently always on top. + bool is_always_on_top_; + + // We need to own the text of the menu, the Windows API does not copy it. + std::wstring always_on_top_menu_text_; + + // Whether or not the client view should be inserted into the Window's view + // hierarchy. + // TODO(beng): (Cleanup) This is probably a short term measure until I figure + // out a way to make other Window subclasses (e.g. + // ConstrainedWindowImpl) and their users jive with the new + // dialog framework. + bool use_client_view_; + + // True if the window was Accepted by the user using the OK button. + bool accepted_; + + // Set to true if the window is in the process of closing . + bool window_closed_; + + DISALLOW_EVIL_CONSTRUCTORS(Window); +}; + +} + +#endif // CHROME_VIEWS_WINDOW_H__ diff --git a/chrome/views/window_delegate.h b/chrome/views/window_delegate.h new file mode 100644 index 0000000..17b81f0 --- /dev/null +++ b/chrome/views/window_delegate.h @@ -0,0 +1,135 @@ +// 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. + +#ifndef CHROME_VIEWS_WINDOW_DELEGATE_H__ +#define CHROME_VIEWS_WINDOW_DELEGATE_H__ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> +#include <string> + +#include "skia/include/SkBitmap.h" + +namespace ChromeViews { + +class DialogDelegate; +class View; + +/////////////////////////////////////////////////////////////////////////////// +// +// WindowDelegate +// +// WindowDelegate is an interface implemented by objects that wish to show a +// Window. The window that is displayed uses this interface to determine how +// it should be displayed and notify the delegate object of certain events. +// +/////////////////////////////////////////////////////////////////////////////// +class WindowDelegate { + public: + virtual DialogDelegate* AsDialogDelegate() { return NULL; } + + // Returns true if the window can be resized. + virtual bool CanResize() const { + return false; + } + + // Returns true if the window can be maximized. + virtual bool CanMaximize() const { + return false; + } + + // Returns true if the window should be placed on top of all other windows on + // the system, even when it is not active. If HasAlwaysOnTopMenu() returns + // true, then this method is only used the first time the window is opened, it + // is stored in the preferences for next runs. + virtual bool IsAlwaysOnTop() const { + return false; + } + + // Returns whether an "always on top" menu should be added to the system menu + // of the window. + virtual bool HasAlwaysOnTopMenu() const { + return false; + } + + // Returns true if the dialog should be displayed modally to the window that + // opened it. Only windows with WindowType == DIALOG can be modal. + virtual bool IsModal() const { + return false; + } + + // Returns the text to be displayed in the window title. + virtual std::wstring GetWindowTitle() const { + return L""; + } + + // Returns the view that should have the focus when the dialog is opened. If + // NULL no view is focused. + virtual View* GetInitiallyFocusedView() const { return NULL; } + + // Returns true if the window should show a title in the title bar. + virtual bool ShouldShowWindowTitle() const { + return true; + } + + // Returns the icon to be displayed in the window. + virtual SkBitmap GetWindowIcon() { + return SkBitmap(); + } + + // Returns true if a window icon should be shown. + virtual bool ShouldShowWindowIcon() const { + return false; + } + + // Execute a command in the window's controller. + virtual void ExecuteWindowsCommand(int command_id) { } + + // Saves the specified bounds, maximized and always on top state as the + // window's position to/ be restored the next time it is shown. + virtual void SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top) { } + + // returns true if there was a saved position, false if there was none and + // the default should be used. + virtual bool RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top) { + return false; + } + + // Called when the window closes. + virtual void WindowClosing() { } +}; + +} // namespace ChromeViews + +#endif // #ifndef CHROME_VIEWS_WINDOW_DELEGATE_H__ |