summaryrefslogtreecommitdiffstats
path: root/chrome/views
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
commit09911bf300f1a419907a9412154760efd0b7abc3 (patch)
treef131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/views
parent586acc5fe142f498261f52c66862fa417c3d52d2 (diff)
downloadchromium_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')
-rw-r--r--chrome/views/SConscript124
-rw-r--r--chrome/views/accelerator.cc141
-rw-r--r--chrome/views/accelerator.h120
-rw-r--r--chrome/views/accelerator_handler.cc61
-rw-r--r--chrome/views/accelerator_handler.h53
-rw-r--r--chrome/views/accessibility/accessible_wrapper.cc105
-rw-r--r--chrome/views/accessibility/accessible_wrapper.h80
-rw-r--r--chrome/views/accessibility/autocomplete_accessibility.cc291
-rw-r--r--chrome/views/accessibility/autocomplete_accessibility.h139
-rw-r--r--chrome/views/accessibility/view_accessibility.cc703
-rw-r--r--chrome/views/accessibility/view_accessibility.h155
-rw-r--r--chrome/views/aero_tooltip_manager.cc144
-rw-r--r--chrome/views/aero_tooltip_manager.h81
-rw-r--r--chrome/views/app_modal_dialog_delegate.h49
-rw-r--r--chrome/views/background.cc128
-rw-r--r--chrome/views/background.h104
-rw-r--r--chrome/views/base_button.cc333
-rw-r--r--chrome/views/base_button.h211
-rw-r--r--chrome/views/bitmap_scroll_bar.cc749
-rw-r--r--chrome/views/bitmap_scroll_bar.h209
-rw-r--r--chrome/views/border.cc129
-rw-r--r--chrome/views/border.h84
-rw-r--r--chrome/views/button.cc230
-rw-r--r--chrome/views/button.h160
-rw-r--r--chrome/views/button_dropdown.cc204
-rw-r--r--chrome/views/button_dropdown.h93
-rw-r--r--chrome/views/checkbox.cc204
-rw-r--r--chrome/views/checkbox.h109
-rw-r--r--chrome/views/chrome_menu.cc2743
-rw-r--r--chrome/views/chrome_menu.h990
-rw-r--r--chrome/views/client_view.cc425
-rw-r--r--chrome/views/client_view.h129
-rw-r--r--chrome/views/combo_box.cc185
-rw-r--r--chrome/views/combo_box.h103
-rw-r--r--chrome/views/controller.h61
-rw-r--r--chrome/views/custom_frame_window.cc1255
-rw-r--r--chrome/views/custom_frame_window.h158
-rw-r--r--chrome/views/decision.cc186
-rw-r--r--chrome/views/decision.h90
-rw-r--r--chrome/views/dialog_delegate.h126
-rw-r--r--chrome/views/event.cc112
-rw-r--r--chrome/views/event.h351
-rw-r--r--chrome/views/external_focus_tracker.cc91
-rw-r--r--chrome/views/external_focus_tracker.h100
-rw-r--r--chrome/views/focus_manager.cc794
-rw-r--r--chrome/views/focus_manager.h354
-rw-r--r--chrome/views/focus_manager_unittest.cc688
-rw-r--r--chrome/views/grid_layout.cc1037
-rw-r--r--chrome/views/grid_layout.h378
-rw-r--r--chrome/views/grid_layout_unittest.cc540
-rw-r--r--chrome/views/group_table_view.cc192
-rw-r--r--chrome/views/group_table_view.h98
-rw-r--r--chrome/views/hwnd_notification_source.h49
-rw-r--r--chrome/views/hwnd_view.cc227
-rw-r--r--chrome/views/hwnd_view.h122
-rw-r--r--chrome/views/hwnd_view_container.cc877
-rw-r--r--chrome/views/hwnd_view_container.h525
-rw-r--r--chrome/views/image_view.cc194
-rw-r--r--chrome/views/image_view.h134
-rw-r--r--chrome/views/label.cc406
-rw-r--r--chrome/views/label.h200
-rw-r--r--chrome/views/layout_manager.cc42
-rw-r--r--chrome/views/layout_manager.h83
-rw-r--r--chrome/views/link.cc212
-rw-r--r--chrome/views/link.h115
-rw-r--r--chrome/views/menu.cc495
-rw-r--r--chrome/views/menu.h369
-rw-r--r--chrome/views/menu_button.cc280
-rw-r--r--chrome/views/menu_button.h122
-rw-r--r--chrome/views/message_box_view.cc206
-rw-r--r--chrome/views/message_box_view.h125
-rw-r--r--chrome/views/native_button.cc212
-rw-r--r--chrome/views/native_button.h156
-rw-r--r--chrome/views/native_control.cc381
-rw-r--r--chrome/views/native_control.h151
-rw-r--r--chrome/views/native_scroll_bar.cc391
-rw-r--r--chrome/views/native_scroll_bar.h88
-rw-r--r--chrome/views/painter.cc190
-rw-r--r--chrome/views/painter.h145
-rw-r--r--chrome/views/radio_button.cc146
-rw-r--r--chrome/views/radio_button.h84
-rw-r--r--chrome/views/repeat_controller.cc83
-rw-r--r--chrome/views/repeat_controller.h81
-rw-r--r--chrome/views/resize_corner.cc57
-rw-r--r--chrome/views/resize_corner.h52
-rw-r--r--chrome/views/root_view.cc1003
-rw-r--r--chrome/views/root_view.h373
-rw-r--r--chrome/views/root_view_drop_target.cc142
-rw-r--r--chrome/views/root_view_drop_target.h96
-rw-r--r--chrome/views/scroll_bar.cc72
-rw-r--r--chrome/views/scroll_bar.h126
-rw-r--r--chrome/views/scroll_view.cc534
-rw-r--r--chrome/views/scroll_view.h233
-rw-r--r--chrome/views/separator.cc65
-rw-r--r--chrome/views/separator.h58
-rw-r--r--chrome/views/tabbed_pane.cc274
-rw-r--r--chrome/views/tabbed_pane.h113
-rw-r--r--chrome/views/table_view.cc1025
-rw-r--r--chrome/views/table_view.h521
-rw-r--r--chrome/views/text_button.cc329
-rw-r--r--chrome/views/text_button.h165
-rw-r--r--chrome/views/text_field.cc1028
-rw-r--r--chrome/views/text_field.h222
-rw-r--r--chrome/views/throbber.cc218
-rw-r--r--chrome/views/throbber.h142
-rw-r--r--chrome/views/tooltip_manager.cc366
-rw-r--r--chrome/views/tooltip_manager.h170
-rw-r--r--chrome/views/tree_node_model.h296
-rw-r--r--chrome/views/tree_view.cc616
-rw-r--r--chrome/views/tree_view.h352
-rw-r--r--chrome/views/view.cc1710
-rw-r--r--chrome/views/view.h1323
-rw-r--r--chrome/views/view_container.h106
-rw-r--r--chrome/views/view_menu_delegate.h59
-rw-r--r--chrome/views/view_storage.cc231
-rw-r--r--chrome/views/view_storage.h105
-rw-r--r--chrome/views/view_unittest.cc534
-rw-r--r--chrome/views/views.vcproj611
-rw-r--r--chrome/views/views.vsprops12
-rw-r--r--chrome/views/window.cc660
-rw-r--r--chrome/views/window.h275
-rw-r--r--chrome/views/window_delegate.h135
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, &current_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, &current_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="..\..\;&quot;$(IntDir)\..\generated_resources\&quot;"
+ />
+</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__