summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
Diffstat (limited to 'views')
-rw-r--r--views/DEPS12
-rw-r--r--views/accelerator.cc130
-rw-r--r--views/accelerator.h101
-rw-r--r--views/accessibility/view_accessibility.cc698
-rw-r--r--views/accessibility/view_accessibility.h144
-rw-r--r--views/accessibility/view_accessibility_wrapper.cc79
-rw-r--r--views/accessibility/view_accessibility_wrapper.h55
-rw-r--r--views/background.cc113
-rw-r--r--views/background.h91
-rw-r--r--views/border.cc106
-rw-r--r--views/border.h59
-rw-r--r--views/controls/button/button.cc79
-rw-r--r--views/controls/button/button.h74
-rw-r--r--views/controls/button/button_dropdown.cc192
-rw-r--r--views/controls/button/button_dropdown.h62
-rw-r--r--views/controls/button/checkbox.cc172
-rw-r--r--views/controls/button/checkbox.h84
-rw-r--r--views/controls/button/custom_button.cc236
-rw-r--r--views/controls/button/custom_button.h105
-rw-r--r--views/controls/button/image_button.cc154
-rw-r--r--views/controls/button/image_button.h101
-rw-r--r--views/controls/button/menu_button.cc253
-rw-r--r--views/controls/button/menu_button.h92
-rw-r--r--views/controls/button/native_button.cc178
-rw-r--r--views/controls/button/native_button.h100
-rw-r--r--views/controls/button/native_button_win.cc234
-rw-r--r--views/controls/button/native_button_win.h105
-rw-r--r--views/controls/button/native_button_wrapper.h62
-rw-r--r--views/controls/button/radio_button.cc107
-rw-r--r--views/controls/button/radio_button.h43
-rw-r--r--views/controls/button/text_button.cc325
-rw-r--r--views/controls/button/text_button.h138
-rw-r--r--views/controls/combo_box.cc179
-rw-r--r--views/controls/combo_box.h80
-rw-r--r--views/controls/hwnd_view.cc137
-rw-r--r--views/controls/hwnd_view.h66
-rw-r--r--views/controls/image_view.cc170
-rw-r--r--views/controls/image_view.h107
-rw-r--r--views/controls/label.cc444
-rw-r--r--views/controls/label.h250
-rw-r--r--views/controls/label_unittest.cc441
-rw-r--r--views/controls/link.cc183
-rw-r--r--views/controls/link.h94
-rw-r--r--views/controls/menu/chrome_menu.cc2816
-rw-r--r--views/controls/menu/chrome_menu.h948
-rw-r--r--views/controls/menu/controller.h33
-rw-r--r--views/controls/menu/menu.cc626
-rw-r--r--views/controls/menu/menu.h355
-rw-r--r--views/controls/menu/view_menu_delegate.h34
-rw-r--r--views/controls/message_box_view.cc209
-rw-r--r--views/controls/message_box_view.h94
-rw-r--r--views/controls/native_control.cc385
-rw-r--r--views/controls/native_control.h132
-rw-r--r--views/controls/native_control_win.cc201
-rw-r--r--views/controls/native_control_win.h100
-rw-r--r--views/controls/native_view_host.cc74
-rw-r--r--views/controls/native_view_host.h103
-rw-r--r--views/controls/scroll_view.cc517
-rw-r--r--views/controls/scroll_view.h207
-rw-r--r--views/controls/scrollbar/bitmap_scroll_bar.cc703
-rw-r--r--views/controls/scrollbar/bitmap_scroll_bar.h192
-rw-r--r--views/controls/scrollbar/native_scroll_bar.cc357
-rw-r--r--views/controls/scrollbar/native_scroll_bar.h67
-rw-r--r--views/controls/scrollbar/scroll_bar.cc47
-rw-r--r--views/controls/scrollbar/scroll_bar.h102
-rw-r--r--views/controls/separator.cc37
-rw-r--r--views/controls/separator.h34
-rw-r--r--views/controls/single_split_view.cc116
-rw-r--r--views/controls/single_split_view.h57
-rw-r--r--views/controls/tabbed_pane.cc264
-rw-r--r--views/controls/tabbed_pane.h94
-rw-r--r--views/controls/table/group_table_view.cc193
-rw-r--r--views/controls/table/group_table_view.h82
-rw-r--r--views/controls/table/table_view.cc1570
-rw-r--r--views/controls/table/table_view.h676
-rw-r--r--views/controls/table/table_view_unittest.cc381
-rw-r--r--views/controls/text_field.cc1192
-rw-r--r--views/controls/text_field.h208
-rw-r--r--views/controls/throbber.cc170
-rw-r--r--views/controls/throbber.h111
-rw-r--r--views/controls/tree/tree_model.h91
-rw-r--r--views/controls/tree/tree_node_iterator.h74
-rw-r--r--views/controls/tree/tree_node_iterator_unittest.cc41
-rw-r--r--views/controls/tree/tree_node_model.h283
-rw-r--r--views/controls/tree/tree_view.cc745
-rw-r--r--views/controls/tree/tree_view.h305
-rw-r--r--views/event.cc42
-rw-r--r--views/event.h324
-rw-r--r--views/event_gtk.cc36
-rw-r--r--views/event_win.cc65
-rw-r--r--views/fill_layout.cc33
-rw-r--r--views/fill_layout.h35
-rw-r--r--views/focus/external_focus_tracker.cc65
-rw-r--r--views/focus/external_focus_tracker.h76
-rw-r--r--views/focus/focus_manager.cc716
-rw-r--r--views/focus/focus_manager.h343
-rw-r--r--views/focus/focus_manager_unittest.cc666
-rw-r--r--views/focus/focus_util_win.cc118
-rw-r--r--views/focus/focus_util_win.h28
-rw-r--r--views/focus/view_storage.cc182
-rw-r--r--views/focus/view_storage.h78
-rw-r--r--views/grid_layout.cc1013
-rw-r--r--views/grid_layout.h354
-rw-r--r--views/grid_layout_unittest.cc514
-rw-r--r--views/layout_manager.cc15
-rw-r--r--views/layout_manager.h60
-rw-r--r--views/painter.cc165
-rw-r--r--views/painter.h120
-rw-r--r--views/repeat_controller.cc45
-rw-r--r--views/repeat_controller.h53
-rw-r--r--views/view.cc1643
-rw-r--r--views/view.h1351
-rw-r--r--views/view_constants.cc13
-rw-r--r--views/view_constants.h25
-rw-r--r--views/view_gtk.cc56
-rw-r--r--views/view_unittest.cc1009
-rw-r--r--views/view_win.cc95
-rw-r--r--views/views.vcproj869
-rw-r--r--views/views.vsprops12
-rw-r--r--views/widget/accelerator_handler.cc46
-rw-r--r--views/widget/accelerator_handler.h30
-rw-r--r--views/widget/aero_tooltip_manager.cc128
-rw-r--r--views/widget/aero_tooltip_manager.h57
-rw-r--r--views/widget/root_view.cc1001
-rw-r--r--views/widget/root_view.h363
-rw-r--r--views/widget/root_view_drop_target.cc118
-rw-r--r--views/widget/root_view_drop_target.h75
-rw-r--r--views/widget/root_view_gtk.cc28
-rw-r--r--views/widget/root_view_win.cc70
-rw-r--r--views/widget/tooltip_manager.cc447
-rw-r--r--views/widget/tooltip_manager.h168
-rw-r--r--views/widget/widget.h81
-rw-r--r--views/widget/widget_gtk.cc386
-rw-r--r--views/widget/widget_gtk.h125
-rw-r--r--views/widget/widget_win.cc999
-rw-r--r--views/widget/widget_win.h636
-rw-r--r--views/window/client_view.cc61
-rw-r--r--views/window/client_view.h84
-rw-r--r--views/window/custom_frame_view.cc695
-rw-r--r--views/window/custom_frame_view.h122
-rw-r--r--views/window/dialog_client_view.cc440
-rw-r--r--views/window/dialog_client_view.h123
-rw-r--r--views/window/dialog_delegate.cc54
-rw-r--r--views/window/dialog_delegate.h113
-rw-r--r--views/window/native_frame_view.cc60
-rw-r--r--views/window/native_frame_view.h39
-rw-r--r--views/window/non_client_view.cc255
-rw-r--r--views/window/non_client_view.h227
-rw-r--r--views/window/window.h135
-rw-r--r--views/window/window_delegate.cc96
-rw-r--r--views/window/window_delegate.h162
-rw-r--r--views/window/window_resources.h30
-rw-r--r--views/window/window_win.cc1446
-rw-r--r--views/window/window_win.h307
154 files changed, 41087 insertions, 0 deletions
diff --git a/views/DEPS b/views/DEPS
new file mode 100644
index 0000000..8ff5f48
--- /dev/null
+++ b/views/DEPS
@@ -0,0 +1,12 @@
+include_rules = [
+ "+app",
+ "+chrome/app",
+ "+chrome/app/theme",
+ "+chrome/browser",
+ "+chrome/browser/views",
+ "+chrome/common",
+ "+grit", # For generated headers
+ "+skia/ext",
+ "+skia/include",
+ "+webkit/glue",
+]
diff --git a/views/accelerator.cc b/views/accelerator.cc
new file mode 100644
index 0000000..b9a474a
--- /dev/null
+++ b/views/accelerator.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/accelerator.h"
+
+#include <windows.h>
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "grit/generated_resources.h"
+
+namespace views {
+
+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;
+ case VK_F1:
+ string_id = IDS_F1_KEY;
+ break;
+ case VK_F11:
+ string_id = IDS_F11_KEY;
+ break;
+ }
+
+ std::wstring shortcut;
+ if (!string_id) {
+ // Our fallback is to try translate the key code to a regular character
+ // unless it is one of digits (VK_0 to VK_9). Some keyboard
+ // layouts have characters other than digits assigned in
+ // an unshifted mode (e.g. French AZERY layout has 'a with grave
+ // accent' for '0'). For display in the menu (e.g. Ctrl-0 for the
+ // default zoom level), we leave VK_[0-9] alone without translation.
+ wchar_t key;
+ if (key_code_ >= '0' && key_code_ <= '9')
+ key = key_code_;
+ else
+ 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
+ // views::Menu we use views::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 views
diff --git a/views/accelerator.h b/views/accelerator.h
new file mode 100644
index 0000000..bb8f91f
--- /dev/null
+++ b/views/accelerator.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// 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 VIEWS_ACCELERATOR_H_
+#define VIEWS_ACCELERATOR_H_
+
+#include <string>
+
+#include "views/event.h"
+
+namespace views {
+
+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& 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 operator !=(const Accelerator& rhs) const {
+ return !(*this == rhs);
+ }
+
+ 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 // VIEWS_ACCELERATOR_H_
diff --git a/views/accessibility/view_accessibility.cc b/views/accessibility/view_accessibility.cc
new file mode 100644
index 0000000..0e2f874
--- /dev/null
+++ b/views/accessibility/view_accessibility.cc
@@ -0,0 +1,698 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/accessibility/view_accessibility.h"
+
+#include "views/accessibility/view_accessibility_wrapper.h"
+#include "views/widget/widget.h"
+
+HRESULT ViewAccessibility::Initialize(views::View* view) {
+ if (!view) {
+ return E_INVALIDARG;
+ }
+
+ view_ = view;
+ 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;
+ }
+
+ gfx::Point pt(x_left, y_top);
+ views::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;
+ views::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);
+ views::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.
+ views::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 ((GetViewAccessibilityWrapper(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::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;
+ }
+
+ gfx::Rect view_bounds;
+ // Retrieving the parent View to be used for converting from view-to-screen
+ // coordinates.
+ views::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_bounds = 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_bounds = view_->GetChildViewAt(var_id.lVal - 1)->bounds();
+ // Parent View is current View.
+ parent = view_;
+ }
+
+ if (!view_bounds.IsEmpty()) {
+ *width = view_bounds.width();
+ *height = view_bounds.height();
+
+ gfx::Point topleft(view_bounds.origin());
+ views::View::ConvertPointToScreen(parent, &topleft);
+ *x_left = topleft.x();
+ *y_top = topleft.y();
+ } else {
+ return E_FAIL;
+ }
+ return S_OK;
+}
+
+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;
+ }
+
+ views::View* child = view_->GetChildViewAt(child_id);
+
+ if (child->GetChildViewCount() != 0) {
+ end->vt = VT_DISPATCH;
+ if ((GetViewAccessibilityWrapper(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.
+ views::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;
+ }
+ }
+
+ views::View* child = parent->GetChildViewAt(view_index);
+ if (child->GetChildViewCount() != 0) {
+ end->vt = VT_DISPATCH;
+ // Retrieve IDispatch for non-leaf child.
+ if ((GetViewAccessibilityWrapper(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_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;
+ }
+
+ views::View* child_view = NULL;
+ bool get_iaccessible = false;
+
+ // Check to see if child is out-of-bounds.
+ if (IsValidChild((var_child.lVal - 1), view_)) {
+ child_view = view_->GetChildViewAt(var_child.lVal - 1);
+ } else {
+ // Child is located elsewhere in the hierarchy, get ID and adjust for MSAA.
+ child_view = view_->GetViewByID(static_cast<int>(var_child.lVal));
+ get_iaccessible = true;
+ }
+
+ if (!child_view) {
+ // No child found.
+ *disp_child = NULL;
+ return E_FAIL;
+ }
+
+ if (get_iaccessible || child_view->GetChildViewCount() != 0) {
+ // Retrieve the IUnknown interface for the requested child view, and
+ // assign the IDispatch returned.
+ if ((GetViewAccessibilityWrapper(child_view))->
+ 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_accChildCount(LONG* child_count) {
+ if (!child_count || !view_) {
+ return E_INVALIDARG;
+ }
+
+ *child_count = view_->GetChildViewCount();
+ 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;
+ }
+
+ 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;
+ }
+
+ 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_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;
+ }
+
+ 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;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP ViewAccessibility::get_accParent(IDispatch** disp_parent) {
+ if (!disp_parent) {
+ return E_INVALIDARG;
+ }
+
+ views::View* parent_view = view_->GetParent();
+
+ if (!parent_view) {
+ // This function can get called during teardown of WidetWin so we
+ // should bail out if we fail to get the HWND.
+ if (!view_->GetWidget() || !view_->GetWidget()->GetNativeView()) {
+ *disp_parent = NULL;
+ return S_FALSE;
+ }
+
+ // 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_->GetWidget()->GetNativeView(),
+ 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 ((GetViewAccessibilityWrapper(parent_view))->
+ 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::get_accRole(VARIANT var_id, VARIANT* role) {
+ if (var_id.vt != VT_I4 || !role) {
+ return E_INVALIDARG;
+ }
+
+ AccessibilityTypes::Role acc_role;
+
+ if (var_id.lVal == CHILDID_SELF) {
+ // Retrieve parent role.
+ if (!view_->GetAccessibleRole(&acc_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(&acc_role)) {
+ role->vt = VT_EMPTY;
+ return E_FAIL;
+ }
+ }
+
+ role->vt = VT_I4;
+ role->lVal = MSAARole(acc_role);
+ 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));
+ }
+
+ // Make sure that state is not empty, and has the proper type.
+ if (state->vt == VT_EMPTY)
+ return E_FAIL;
+
+ return S_OK;
+}
+
+// Helper functions.
+
+bool ViewAccessibility::IsValidChild(int child_id, views::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* msaa_state, views::View* view) {
+ // Default state; all views can have accessibility focus.
+ msaa_state->lVal |= STATE_SYSTEM_FOCUSABLE;
+
+ if (!view)
+ return;
+
+ if (!view->IsEnabled()) {
+ msaa_state->lVal |= STATE_SYSTEM_UNAVAILABLE;
+ }
+ if (!view->IsVisible()) {
+ msaa_state->lVal |= STATE_SYSTEM_INVISIBLE;
+ }
+ if (view->IsHotTracked()) {
+ msaa_state->lVal |= STATE_SYSTEM_HOTTRACKED;
+ }
+ if (view->IsPushed()) {
+ msaa_state->lVal |= STATE_SYSTEM_PRESSED;
+ }
+ // Check both for actual View focus, as well as accessibility focus.
+ views::View* parent = view->GetParent();
+
+ if (view->HasFocus() ||
+ (parent && parent->GetAccFocusedChildView() == view)) {
+ msaa_state->lVal |= STATE_SYSTEM_FOCUSED;
+ }
+
+ // Add on any view-specific states.
+ AccessibilityTypes::State state;
+ view->GetAccessibleState(&state);
+
+ msaa_state->lVal |= MSAAState(state);
+}
+
+long ViewAccessibility::MSAARole(AccessibilityTypes::Role role) {
+ switch (role) {
+ case AccessibilityTypes::ROLE_APPLICATION :
+ return ROLE_SYSTEM_APPLICATION;
+ case AccessibilityTypes::ROLE_BUTTONDROPDOWN :
+ return ROLE_SYSTEM_BUTTONDROPDOWN;
+ case AccessibilityTypes::ROLE_GROUPING :
+ return ROLE_SYSTEM_GROUPING;
+ case AccessibilityTypes::ROLE_PAGETAB :
+ return ROLE_SYSTEM_PAGETAB;
+ case AccessibilityTypes::ROLE_PUSHBUTTON :
+ return ROLE_SYSTEM_PUSHBUTTON;
+ case AccessibilityTypes::ROLE_TEXT :
+ return ROLE_SYSTEM_TEXT;
+ case AccessibilityTypes::ROLE_TOOLBAR :
+ return ROLE_SYSTEM_TOOLBAR;
+ case AccessibilityTypes::ROLE_CLIENT :
+ default:
+ // This is the default role for MSAA.
+ return ROLE_SYSTEM_CLIENT;
+ }
+}
+
+long ViewAccessibility::MSAAState(AccessibilityTypes::State state) {
+ switch (state) {
+ case AccessibilityTypes::STATE_HASPOPUP :
+ return STATE_SYSTEM_HASPOPUP;
+ case AccessibilityTypes::STATE_READONLY :
+ return STATE_SYSTEM_READONLY;
+ default :
+ // No default state in MSAA.
+ return 0;
+ }
+}
+
+// IAccessible functions not supported.
+
+HRESULT ViewAccessibility::accDoDefaultAction(VARIANT var_id) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::get_accValue(VARIANT var_id, BSTR* value) {
+ if (value)
+ *value = NULL;
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::get_accSelection(VARIANT* selected) {
+ if (selected)
+ selected->vt = VT_EMPTY;
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::accSelect(LONG flagsSelect, VARIANT var_id) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::get_accHelp(VARIANT var_id, BSTR* help) {
+ if (help)
+ *help = NULL;
+ return E_NOTIMPL;
+}
+
+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 E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::put_accName(VARIANT var_id, BSTR put_name) {
+ // Deprecated.
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ViewAccessibility::put_accValue(VARIANT var_id, BSTR put_val) {
+ // Deprecated.
+ return E_NOTIMPL;
+}
diff --git a/views/accessibility/view_accessibility.h b/views/accessibility/view_accessibility.h
new file mode 100644
index 0000000..840a685
--- /dev/null
+++ b/views/accessibility/view_accessibility.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H_
+#define VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include <oleacc.h>
+
+#include "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(views::View* view);
+
+ // Supported IAccessible methods.
+
+ // 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 current screen location.
+ STDMETHODIMP accLocation(LONG* x_left,
+ LONG* y_top,
+ LONG* width,
+ LONG* height,
+ VARIANT var_id);
+
+ // Traverses to another UI element and retrieves the object.
+ STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end);
+
+ // Retrieves an IDispatch interface pointer for the specified child.
+ STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child);
+
+ // Retrieves the number of accessible children.
+ STDMETHODIMP get_accChildCount(LONG* child_count);
+
+ // Retrieves a string that describes the object's default action.
+ STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action);
+
+ // Retrieves the tooltip description.
+ STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc);
+
+ // Retrieves the object that has the keyboard focus.
+ STDMETHODIMP get_accFocus(VARIANT* focus_child);
+
+ // Retrieves the specified object's shortcut.
+ STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key);
+
+ // Retrieves the name of the specified object.
+ STDMETHODIMP get_accName(VARIANT var_id, BSTR* name);
+
+ // Retrieves the IDispatch interface of the object's parent.
+ STDMETHODIMP get_accParent(IDispatch** disp_parent);
+
+ // Retrieves information describing the role of the specified object.
+ STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role);
+
+ // Retrieves the current state of the specified object.
+ STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state);
+
+ // 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, views::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.
+ ViewAccessibilityWrapper* GetViewAccessibilityWrapper(views::View* v) const {
+ return v->GetViewAccessibilityWrapper();
+ }
+
+ // Helper function which sets applicable states of view.
+ void SetState(VARIANT* msaa_state, views::View* view);
+
+ // Returns a conversion from the Role (as defined in
+ // chrome/common/accessibility_types.h) to an MSAA role.
+ long MSAARole(AccessibilityTypes::Role role);
+
+ // Returns a conversion from the State (as defined in
+ // chrome/common/accessibility_types.h) to MSAA states set.
+ long MSAAState(AccessibilityTypes::State state);
+
+ // Member View needed for view-specific calls.
+ views::View* view_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ViewAccessibility);
+};
+
+#endif // VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H_
diff --git a/views/accessibility/view_accessibility_wrapper.cc b/views/accessibility/view_accessibility_wrapper.cc
new file mode 100644
index 0000000..ff336ae
--- /dev/null
+++ b/views/accessibility/view_accessibility_wrapper.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/accessibility/view_accessibility_wrapper.h"
+
+#include "views/accessibility/view_accessibility.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ViewAccessibilityWrapper - constructors, destructors
+//
+////////////////////////////////////////////////////////////////////////////////
+
+ViewAccessibilityWrapper::ViewAccessibilityWrapper(views::View* view)
+ : accessibility_info_(NULL),
+ view_(view) {
+}
+
+STDMETHODIMP ViewAccessibilityWrapper::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);
+
+ if (!SUCCEEDED(hr) || !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 ViewAccessibilityWrapper::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 ViewAccessibilityWrapper::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/views/accessibility/view_accessibility_wrapper.h b/views/accessibility/view_accessibility_wrapper.h
new file mode 100644
index 0000000..4390662
--- /dev/null
+++ b/views/accessibility/view_accessibility_wrapper.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_WRAPPER_H_
+#define VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_WRAPPER_H_
+
+#include <atlcomcli.h>
+#include <oleacc.h>
+
+#include "base/basictypes.h"
+
+namespace views {
+class View;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ViewAccessibilityWrapper
+//
+// 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 ViewAccessibilityWrapper {
+ public:
+ explicit ViewAccessibilityWrapper(views::View* view);
+ ~ViewAccessibilityWrapper() {}
+
+ 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.
+ views::View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewAccessibilityWrapper);
+};
+
+#endif // VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_WRAPPER_H_
diff --git a/views/background.cc b/views/background.cc
new file mode 100644
index 0000000..f2bd176
--- /dev/null
+++ b/views/background.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/background.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/logging.h"
+#include "skia/ext/skia_utils_win.h"
+#include "skia/include/SkPaint.h"
+#include "views/painter.h"
+#include "views/view.h"
+
+namespace views {
+
+// 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 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->width(), view->height(), canvas,
+ painter_);
+ }
+
+ private:
+ bool owns_painter_;
+ Painter* painter_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BackgroundPainter);
+};
+
+Background::Background()
+#if defined(OS_WIN)
+ : native_control_brush_(NULL)
+#endif
+{
+}
+
+Background::~Background() {
+#if defined(OS_WIN)
+ DeleteObject(native_control_brush_);
+#endif
+}
+
+void Background::SetNativeControlColor(SkColor color) {
+#if defined(OS_WIN)
+ DeleteObject(native_control_brush_);
+ native_control_brush_ = CreateSolidBrush(skia::SkColorToCOLORREF(color));
+#endif
+}
+
+//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);
+}
+
+} // namespace views
diff --git a/views/background.h b/views/background.h
new file mode 100644
index 0000000..acc3948
--- /dev/null
+++ b/views/background.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_BACKGROUND_H_
+#define VIEWS_BACKGROUND_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif // defined(OS_WIN)
+
+#include "base/basictypes.h"
+#include "skia/include/SkColor.h"
+
+class ChromeCanvas;
+
+namespace views {
+
+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);
+
+#if defined(OS_WIN)
+ // TODO(port): Make GetNativeControlBrush portable (currently uses HBRUSH).
+
+ // Get the brush that was specified by SetNativeControlColor
+ HBRUSH GetNativeControlBrush() const { return native_control_brush_; };
+#endif // defined(OS_WIN)
+
+ private:
+#if defined(OS_WIN)
+ // TODO(port): Create portable replacement for HBRUSH.
+ HBRUSH native_control_brush_;
+#endif // defined(OS_WIN)
+ DISALLOW_COPY_AND_ASSIGN(Background);
+};
+
+} // namespace views
+
+#endif // VIEWS_BACKGROUND_H_
diff --git a/views/border.cc b/views/border.cc
new file mode 100644
index 0000000..9127bce
--- /dev/null
+++ b/views/border.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/border.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/logging.h"
+
+namespace views {
+
+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.width(), insets_.top());
+ if (clip_rect.Intersects(border_bounds))
+ canvas->FillRectInt(color_, 0, 0, view.width(), insets_.top());
+ // Left border.
+ border_bounds.SetRect(0, 0, insets_.left(), view.height());
+ if (clip_rect.Intersects(border_bounds))
+ canvas->FillRectInt(color_, 0, 0, insets_.left(), view.height());
+ // Bottom border.
+ border_bounds.SetRect(0, view.height() - insets_.bottom(),
+ view.width(), insets_.bottom());
+ if (clip_rect.Intersects(border_bounds))
+ canvas->FillRectInt(color_, 0, view.height() - insets_.bottom(),
+ view.width(), insets_.bottom());
+ // Right border.
+ border_bounds.SetRect(view.width() - insets_.right(), 0,
+ insets_.right(), view.height());
+ if (clip_rect.Intersects(border_bounds))
+ canvas->FillRectInt(color_, view.width() - insets_.right(), 0,
+ insets_.right(), view.height());
+}
+
+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 views
diff --git a/views/border.h b/views/border.h
new file mode 100644
index 0000000..7e7e2bd
--- /dev/null
+++ b/views/border.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_BORDER_H_
+#define VIEWS_BORDER_H_
+
+#include "app/gfx/insets.h"
+#include "skia/include/SkColor.h"
+#include "views/view.h"
+
+class ChromeCanvas;
+
+namespace views {
+
+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_COPY_AND_ASSIGN(Border);
+};
+
+} // namespace views
+
+#endif // VIEWS_BORDER_H_
diff --git a/views/controls/button/button.cc b/views/controls/button/button.cc
new file mode 100644
index 0000000..cbb42bf
--- /dev/null
+++ b/views/controls/button/button.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/button.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// Button, public:
+
+Button::~Button() {
+}
+
+void Button::SetTooltipText(const std::wstring& tooltip_text) {
+ tooltip_text_ = tooltip_text;
+ TooltipTextChanged();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Button, View overrides:
+
+bool Button::GetTooltipText(int x, int y, std::wstring* tooltip) {
+ if (!tooltip_text_.empty()) {
+ *tooltip = tooltip_text_;
+ return true;
+ }
+ return false;
+}
+
+bool Button::GetAccessibleKeyboardShortcut(std::wstring* shortcut) {
+ if (!accessible_shortcut_.empty()) {
+ *shortcut = accessible_shortcut_;
+ return true;
+ }
+ return false;
+}
+
+bool Button::GetAccessibleName(std::wstring* name) {
+ if (!accessible_name_.empty()) {
+ *name = accessible_name_;
+ return true;
+ }
+ return false;
+}
+
+bool Button::GetAccessibleRole(AccessibilityTypes::Role* role) {
+ *role = AccessibilityTypes::ROLE_PUSHBUTTON;
+ return true;
+}
+
+void Button::SetAccessibleKeyboardShortcut(const std::wstring& shortcut) {
+ accessible_shortcut_.assign(shortcut);
+}
+
+void Button::SetAccessibleName(const std::wstring& name) {
+ accessible_name_.assign(name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Button, protected:
+
+Button::Button(ButtonListener* listener)
+ : listener_(listener),
+ tag_(-1),
+ mouse_event_flags_(0) {
+}
+
+void Button::NotifyClick(int mouse_event_flags) {
+ mouse_event_flags_ = mouse_event_flags;
+ // We can be called when there is no listener, in cases like double clicks on
+ // menu buttons etc.
+ if (listener_)
+ listener_->ButtonPressed(this);
+ // NOTE: don't attempt to reset mouse_event_flags_ as the listener may have
+ // deleted us.
+}
+
+} // namespace views
diff --git a/views/controls/button/button.h b/views/controls/button/button.h
new file mode 100644
index 0000000..514758f
--- /dev/null
+++ b/views/controls/button/button.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_BUTTON_H_
+
+#include "views/view.h"
+
+namespace views {
+
+class Button;
+
+// An interface implemented by an object to let it know that a button was
+// pressed.
+class ButtonListener {
+ public:
+ virtual void ButtonPressed(Button* sender) = 0;
+};
+
+// A View representing a button. Depending on the specific type, the button
+// could be implemented by a native control or custom rendered.
+class Button : public View {
+ public:
+ virtual ~Button();
+
+ void SetTooltipText(const std::wstring& tooltip_text);
+
+ int tag() const { return tag_; }
+ void set_tag(int tag) { tag_ = tag; }
+
+ int mouse_event_flags() const { return mouse_event_flags_; }
+
+ // Overridden from View:
+ virtual bool GetTooltipText(int x, int y, std::wstring* tooltip);
+ virtual bool GetAccessibleKeyboardShortcut(std::wstring* shortcut);
+ virtual bool GetAccessibleName(std::wstring* name);
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role);
+ virtual void SetAccessibleKeyboardShortcut(const std::wstring& shortcut);
+ virtual void SetAccessibleName(const std::wstring& name);
+
+ protected:
+ // Construct the Button with a Listener. The listener can be NULL. This can be
+ // true of buttons that don't have a listener - e.g. menubuttons where there's
+ // no default action and checkboxes.
+ explicit Button(ButtonListener* listener);
+
+ // Cause the button to notify the listener that a click occurred.
+ virtual void NotifyClick(int mouse_event_flags);
+
+ // The button's listener. Notified when clicked.
+ ButtonListener* listener_;
+
+ private:
+ // The text shown in a tooltip.
+ std::wstring tooltip_text_;
+
+ // Accessibility data.
+ std::wstring accessible_shortcut_;
+ std::wstring accessible_name_;
+
+ // The id tag associated with this button. Used to disambiguate buttons in
+ // the ButtonListener implementation.
+ int tag_;
+
+ // Event flags present when the button was clicked.
+ int mouse_event_flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(Button);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_BUTTON_BUTTON_H_
diff --git a/views/controls/button/button_dropdown.cc b/views/controls/button/button_dropdown.cc
new file mode 100644
index 0000000..270894b
--- /dev/null
+++ b/views/controls/button/button_dropdown.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/button_dropdown.h"
+
+#include "app/l10n_util.h"
+#include "base/message_loop.h"
+#include "grit/generated_resources.h"
+#include "views/controls/menu/view_menu_delegate.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+// How long to wait before showing the menu
+static const int kMenuTimerDelay = 500;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ButtonDropDown - constructors, destructors, initialization, cleanup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+ButtonDropDown::ButtonDropDown(ButtonListener* listener,
+ Menu::Delegate* menu_delegate)
+ : ImageButton(listener),
+ menu_delegate_(menu_delegate),
+ y_position_on_lbuttondown_(0),
+ show_menu_factory_(this) {
+}
+
+ButtonDropDown::~ButtonDropDown() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ButtonDropDown - Events
+//
+////////////////////////////////////////////////////////////////////////////////
+
+bool ButtonDropDown::OnMousePressed(const MouseEvent& e) {
+ if (IsEnabled() && e.IsLeftMouseButton() && HitTest(e.location())) {
+ // 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.y();
+
+ // Schedule a task that will show the menu.
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ show_menu_factory_.NewRunnableMethod(&ButtonDropDown::ShowDropDownMenu,
+ GetWidget()->GetNativeView()),
+ kMenuTimerDelay);
+ }
+
+ return ImageButton::OnMousePressed(e);
+}
+
+void ButtonDropDown::OnMouseReleased(const MouseEvent& e, bool canceled) {
+ ImageButton::OnMouseReleased(e, canceled);
+
+ if (canceled)
+ return;
+
+ if (e.IsLeftMouseButton())
+ show_menu_factory_.RevokeAll();
+
+ if (IsEnabled() && e.IsRightMouseButton() && HitTest(e.location())) {
+ 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(GetWidget()->GetNativeView());
+ }
+}
+
+bool ButtonDropDown::OnMouseDragged(const MouseEvent& e) {
+ bool result = ImageButton::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.y() > y_position_on_lbuttondown_ + dragging_threshold) {
+ show_menu_factory_.RevokeAll();
+ ShowDropDownMenu(GetWidget()->GetNativeView());
+ }
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ButtonDropDown - Menu functions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void ButtonDropDown::ShowContextMenu(int x, int y, bool is_mouse_gesture) {
+ 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(GetWidget()->GetNativeView());
+ SetState(BS_HOT);
+}
+
+void ButtonDropDown::ShowDropDownMenu(HWND window) {
+ if (menu_delegate_) {
+ gfx::Rect lb = GetLocalBounds(true);
+
+ // Both the menu position and the menu anchor type change if the UI layout
+ // is right-to-left.
+ gfx::Point menu_position(lb.origin());
+ 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);
+
+ int left_bound = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ if (menu_position.x() < left_bound)
+ menu_position.set_x(left_bound);
+
+ 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(AccessibilityTypes::Role* role) {
+ DCHECK(role);
+
+ *role = AccessibilityTypes::ROLE_BUTTONDROPDOWN;
+ return true;
+}
+
+bool ButtonDropDown::GetAccessibleState(AccessibilityTypes::State* state) {
+ DCHECK(state);
+
+ *state = AccessibilityTypes::STATE_HASPOPUP;
+ return true;
+}
+
+} // namespace views
diff --git a/views/controls/button/button_dropdown.h b/views/controls/button/button_dropdown.h
new file mode 100644
index 0000000..feb5b5c
--- /dev/null
+++ b/views/controls/button/button_dropdown.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_
+#define VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_
+
+#include "base/task.h"
+#include "views/controls/button/image_button.h"
+#include "views/controls/menu/menu.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ButtonDropDown
+//
+// A button class that when pressed (and held) or pressed (and drag down) will
+// display a menu
+//
+////////////////////////////////////////////////////////////////////////////////
+class ButtonDropDown : public ImageButton {
+ public:
+ ButtonDropDown(ButtonListener* listener, Menu::Delegate* menu_delegate);
+ virtual ~ButtonDropDown();
+
+ // Accessibility accessors, overridden from View.
+ virtual bool GetAccessibleDefaultAction(std::wstring* action);
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role);
+ virtual bool GetAccessibleState(AccessibilityTypes::State* state);
+
+ private:
+ // Overridden from Button
+ virtual bool OnMousePressed(const MouseEvent& e);
+ virtual void OnMouseReleased(const MouseEvent& e, bool canceled);
+ virtual bool OnMouseDragged(const MouseEvent& e);
+
+ // Overridden from View. Used to display the right-click menu, as triggered
+ // by the keyboard, for instance. Using the member function ShowDropDownMenu
+ // for the actual display.
+ virtual void ShowContextMenu(int x,
+ int y,
+ bool is_mouse_gesture);
+
+ // 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_COPY_AND_ASSIGN(ButtonDropDown);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_
diff --git a/views/controls/button/checkbox.cc b/views/controls/button/checkbox.cc
new file mode 100644
index 0000000..8783bcf
--- /dev/null
+++ b/views/controls/button/checkbox.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "views/controls/button/checkbox.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "views/controls/label.h"
+
+namespace views {
+
+// static
+const char Checkbox::kViewClassName[] = "views/Checkbox";
+
+static const int kCheckboxLabelSpacing = 4;
+static const int kLabelFocusPaddingHorizontal = 2;
+static const int kLabelFocusPaddingVertical = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+// Checkbox, public:
+
+Checkbox::Checkbox() : NativeButton(NULL), checked_(false) {
+ Init(std::wstring());
+}
+
+Checkbox::Checkbox(const std::wstring& label)
+ : NativeButton(NULL, label),
+ checked_(false) {
+ Init(label);
+}
+
+Checkbox::~Checkbox() {
+}
+
+void Checkbox::SetMultiLine(bool multiline) {
+ label_->SetMultiLine(multiline);
+}
+
+void Checkbox::SetChecked(bool checked) {
+ if (checked_ == checked)
+ return;
+ checked_ = checked;
+ if (native_wrapper_)
+ native_wrapper_->UpdateChecked();
+}
+
+// static
+int Checkbox::GetTextIndent() {
+ return NativeButtonWrapper::GetFixedWidth() + kCheckboxLabelSpacing;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Checkbox, View overrides:
+
+gfx::Size Checkbox::GetPreferredSize() {
+ gfx::Size prefsize = native_wrapper_->GetView()->GetPreferredSize();
+ prefsize.set_width(
+ prefsize.width() + kCheckboxLabelSpacing +
+ kLabelFocusPaddingHorizontal * 2);
+ gfx::Size label_prefsize = label_->GetPreferredSize();
+ prefsize.set_width(prefsize.width() + label_prefsize.width());
+ prefsize.set_height(
+ std::max(prefsize.height(),
+ label_prefsize.height() + kLabelFocusPaddingVertical * 2));
+ return prefsize;
+}
+
+void Checkbox::Layout() {
+ if (!native_wrapper_)
+ return;
+
+ gfx::Size checkmark_prefsize = native_wrapper_->GetView()->GetPreferredSize();
+ int label_x = checkmark_prefsize.width() + kCheckboxLabelSpacing +
+ kLabelFocusPaddingHorizontal;
+ label_->SetBounds(
+ label_x, 0, std::max(0, width() - label_x - kLabelFocusPaddingHorizontal),
+ height());
+ int first_line_height = label_->GetFont().height();
+ native_wrapper_->GetView()->SetBounds(
+ 0, ((first_line_height - checkmark_prefsize.height()) / 2),
+ checkmark_prefsize.width(), checkmark_prefsize.height());
+ native_wrapper_->GetView()->Layout();
+}
+
+void Checkbox::PaintFocusBorder(ChromeCanvas* canvas) {
+ // Our focus border is rendered by the label, so we don't do anything here.
+}
+
+View* Checkbox::GetViewForPoint(const gfx::Point& point) {
+ return GetViewForPoint(point, false);
+}
+
+View* Checkbox::GetViewForPoint(const gfx::Point& point,
+ bool can_create_floating) {
+ return GetLocalBounds(true).Contains(point) ? this : NULL;
+}
+
+void Checkbox::OnMouseEntered(const MouseEvent& e) {
+ native_wrapper_->SetPushed(HitTestLabel(e));
+}
+
+void Checkbox::OnMouseMoved(const MouseEvent& e) {
+ native_wrapper_->SetPushed(HitTestLabel(e));
+}
+
+void Checkbox::OnMouseExited(const MouseEvent& e) {
+ native_wrapper_->SetPushed(false);
+}
+
+bool Checkbox::OnMousePressed(const MouseEvent& e) {
+ native_wrapper_->SetPushed(HitTestLabel(e));
+ return true;
+}
+
+void Checkbox::OnMouseReleased(const MouseEvent& e, bool canceled) {
+ native_wrapper_->SetPushed(false);
+ if (!canceled && HitTestLabel(e)) {
+ SetChecked(!checked());
+ ButtonPressed();
+ }
+}
+
+bool Checkbox::OnMouseDragged(const MouseEvent& e) {
+ return false;
+}
+
+void Checkbox::WillGainFocus() {
+ label_->set_paint_as_focused(true);
+}
+
+void Checkbox::WillLoseFocus() {
+ label_->set_paint_as_focused(false);
+}
+
+std::string Checkbox::GetClassName() const {
+ return kViewClassName;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Checkbox, NativeButton overrides:
+
+void Checkbox::CreateWrapper() {
+ native_wrapper_ = NativeButtonWrapper::CreateCheckboxWrapper(this);
+ native_wrapper_->UpdateLabel();
+ native_wrapper_->UpdateChecked();
+}
+
+void Checkbox::InitBorder() {
+ // No border, so we do nothing.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Checkbox, protected:
+
+bool Checkbox::HitTestLabel(const MouseEvent& e) {
+ gfx::Point tmp(e.location());
+ ConvertPointToView(this, label_, &tmp);
+ return label_->HitTest(tmp);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Checkbox, private:
+
+void Checkbox::Init(const std::wstring& label_text) {
+ set_minimum_size(gfx::Size(0, 0));
+ label_ = new Label(label_text);
+ label_->set_has_focus_border(true);
+ label_->SetHorizontalAlignment(Label::ALIGN_LEFT);
+ AddChildView(label_);
+}
+
+} // namespace views
diff --git a/views/controls/button/checkbox.h b/views/controls/button/checkbox.h
new file mode 100644
index 0000000..db6706b
--- /dev/null
+++ b/views/controls/button/checkbox.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_CHECKBOX_H_
+#define VIEWS_CONTROLS_BUTTON_CHECKBOX_H_
+
+#include "views/controls/button/native_button.h"
+
+namespace views {
+
+class Label;
+
+// A NativeButton subclass representing a checkbox.
+class Checkbox : public NativeButton {
+ public:
+ // The button's class name.
+ static const char kViewClassName[];
+
+ Checkbox();
+ Checkbox(const std::wstring& label);
+ virtual ~Checkbox();
+
+ // Sets a listener for this checkbox. Checkboxes aren't required to have them
+ // since their state can be read independently of them being toggled.
+ void set_listener(ButtonListener* listener) { listener_ = listener; }
+
+ // Sets whether or not the checkbox label should wrap multiple lines of text.
+ // If true, long lines are wrapped, and this is reflected in the preferred
+ // size returned by GetPreferredSize. If false, text that will not fit within
+ // the available bounds for the label will be cropped.
+ void SetMultiLine(bool multiline);
+
+ // Sets/Gets whether or not the checkbox is checked.
+ virtual void SetChecked(bool checked);
+ bool checked() const { return checked_; }
+
+ // Returns the indentation of the text from the left edge of the view.
+ static int GetTextIndent();
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Layout();
+ virtual void PaintFocusBorder(ChromeCanvas* canvas);
+ virtual View* GetViewForPoint(const gfx::Point& point);
+ virtual View* GetViewForPoint(const gfx::Point& point,
+ bool can_create_floating);
+ virtual void OnMouseEntered(const MouseEvent& e);
+ virtual void OnMouseMoved(const MouseEvent& e);
+ virtual void OnMouseExited(const MouseEvent& e);
+ virtual bool OnMousePressed(const MouseEvent& e);
+ virtual void OnMouseReleased(const MouseEvent& e, bool canceled);
+ virtual bool OnMouseDragged(const MouseEvent& e);
+ virtual void WillGainFocus();
+ virtual void WillLoseFocus();
+
+ protected:
+ virtual std::string GetClassName() const;
+
+ // Overridden from NativeButton2:
+ virtual void CreateWrapper();
+ virtual void InitBorder();
+
+ // Returns true if the event (in Checkbox coordinates) is within the bounds of
+ // the label.
+ bool HitTestLabel(const MouseEvent& e);
+
+ private:
+ // Called from the constructor to create and configure the checkbox label.
+ void Init(const std::wstring& label_text);
+
+ // The checkbox's label. We don't use the OS version because of transparency
+ // and sizing issues.
+ Label* label_;
+
+ // True if the checkbox is checked.
+ bool checked_;
+
+ DISALLOW_COPY_AND_ASSIGN(Checkbox);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_BUTTON_CHECKBOX_H_
diff --git a/views/controls/button/custom_button.cc b/views/controls/button/custom_button.cc
new file mode 100644
index 0000000..2b5f43d
--- /dev/null
+++ b/views/controls/button/custom_button.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/custom_button.h"
+
+#include "app/throb_animation.h"
+#include "base/keyboard_codes.h"
+
+namespace views {
+
+// How long the hover animation takes if uninterrupted.
+static const int kHoverFadeDurationMs = 150;
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, public:
+
+CustomButton::~CustomButton() {
+}
+
+void CustomButton::SetState(ButtonState state) {
+ if (state != state_) {
+ if (animate_on_state_change_ || !hover_animation_->IsAnimating()) {
+ animate_on_state_change_ = true;
+ if (state_ == BS_NORMAL && state == BS_HOT) {
+ // Button is hovered from a normal state, start hover animation.
+ hover_animation_->Show();
+ } else if (state_ == BS_HOT && state == BS_NORMAL) {
+ // Button is returning to a normal state from hover, start hover
+ // fade animation.
+ hover_animation_->Hide();
+ } else {
+ hover_animation_->Stop();
+ }
+ }
+
+ state_ = state;
+ SchedulePaint();
+ }
+}
+
+void CustomButton::StartThrobbing(int cycles_til_stop) {
+ animate_on_state_change_ = false;
+ hover_animation_->StartThrobbing(cycles_til_stop);
+}
+
+void CustomButton::SetAnimationDuration(int duration) {
+ hover_animation_->SetSlideDuration(duration);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, View overrides:
+
+void CustomButton::SetEnabled(bool enabled) {
+ if (enabled && state_ == BS_DISABLED) {
+ SetState(BS_NORMAL);
+ } else if (!enabled && state_ != BS_DISABLED) {
+ SetState(BS_DISABLED);
+ }
+}
+
+bool CustomButton::IsEnabled() const {
+ return state_ != BS_DISABLED;
+}
+
+bool CustomButton::IsFocusable() const {
+ return (state_ != BS_DISABLED) && View::IsFocusable();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, protected:
+
+CustomButton::CustomButton(ButtonListener* listener)
+ : Button(listener),
+ state_(BS_NORMAL),
+ animate_on_state_change_(true),
+ triggerable_event_flags_(MouseEvent::EF_LEFT_BUTTON_DOWN) {
+ hover_animation_.reset(new ThrobAnimation(this));
+ hover_animation_->SetSlideDuration(kHoverFadeDurationMs);
+}
+
+bool CustomButton::IsTriggerableEvent(const MouseEvent& e) {
+ return (triggerable_event_flags_ & e.GetFlags()) != 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, View overrides (protected):
+
+bool CustomButton::AcceleratorPressed(const Accelerator& accelerator) {
+ if (enabled_) {
+ SetState(BS_NORMAL);
+ NotifyClick(0);
+ return true;
+ }
+ return false;
+}
+
+bool CustomButton::OnMousePressed(const MouseEvent& e) {
+ if (state_ != BS_DISABLED) {
+ if (IsTriggerableEvent(e) && HitTest(e.location()))
+ SetState(BS_PUSHED);
+ RequestFocus();
+ }
+ return true;
+}
+
+bool CustomButton::OnMouseDragged(const MouseEvent& e) {
+ if (state_ != BS_DISABLED) {
+ if (!HitTest(e.location()))
+ SetState(BS_NORMAL);
+ else if (IsTriggerableEvent(e))
+ SetState(BS_PUSHED);
+ else
+ SetState(BS_HOT);
+ }
+ return true;
+}
+
+void CustomButton::OnMouseReleased(const 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.location())) {
+ 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 CustomButton::OnMouseEntered(const MouseEvent& e) {
+ if (state_ != BS_DISABLED)
+ SetState(BS_HOT);
+}
+
+void CustomButton::OnMouseMoved(const MouseEvent& e) {
+ if (state_ != BS_DISABLED) {
+ if (HitTest(e.location())) {
+ SetState(BS_HOT);
+ } else {
+ SetState(BS_NORMAL);
+ }
+ }
+}
+
+void CustomButton::OnMouseExited(const MouseEvent& e) {
+ // Starting a drag results in a MouseExited, we need to ignore it.
+ if (state_ != BS_DISABLED && !InDrag())
+ SetState(BS_NORMAL);
+}
+
+bool CustomButton::OnKeyPressed(const KeyEvent& e) {
+ if (state_ != BS_DISABLED) {
+ // Space sets button state to pushed. Enter clicks the button. This matches
+ // the Windows native behavior of buttons, where Space clicks the button
+ // on KeyRelease and Enter clicks the button on KeyPressed.
+ if (e.GetCharacter() == base::VKEY_SPACE) {
+ SetState(BS_PUSHED);
+ return true;
+ } else if (e.GetCharacter() == base::VKEY_RETURN) {
+ SetState(BS_NORMAL);
+ NotifyClick(0);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CustomButton::OnKeyReleased(const KeyEvent& e) {
+ if (state_ != BS_DISABLED) {
+ if (e.GetCharacter() == base::VKEY_SPACE) {
+ SetState(BS_NORMAL);
+ NotifyClick(0);
+ return true;
+ }
+ }
+ return false;
+}
+
+void CustomButton::OnDragDone() {
+ SetState(BS_NORMAL);
+}
+
+void CustomButton::ShowContextMenu(int x, int y, bool is_mouse_gesture) {
+ if (GetContextMenuController()) {
+ // We're about to show the context menu. Showing the context menu likely
+ // means we won't get a mouse exited and reset state. Reset it now to be
+ // sure.
+ if (state_ != BS_DISABLED)
+ SetState(BS_NORMAL);
+ View::ShowContextMenu(x, y, is_mouse_gesture);
+ }
+}
+
+void CustomButton::ViewHierarchyChanged(bool is_add, View *parent,
+ View *child) {
+ if (!is_add && state_ != BS_DISABLED)
+ SetState(BS_NORMAL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, AnimationDelegate implementation:
+
+void CustomButton::AnimationProgressed(const Animation* animation) {
+ SchedulePaint();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomButton, private:
+
+void CustomButton::SetHighlighted(bool highlighted) {
+ if (highlighted && state_ != BS_DISABLED) {
+ SetState(BS_HOT);
+ } else if (!highlighted && state_ != BS_DISABLED) {
+ SetState(BS_NORMAL);
+ }
+}
+
+bool CustomButton::IsHighlighted() const {
+ return state_ == BS_HOT;
+}
+
+bool CustomButton::IsPushed() const {
+ return state_ == BS_PUSHED;
+}
+
+} // namespace views
diff --git a/views/controls/button/custom_button.h b/views/controls/button/custom_button.h
new file mode 100644
index 0000000..32ff76b
--- /dev/null
+++ b/views/controls/button/custom_button.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_
+
+#include "app/animation.h"
+#include "views/controls/button/button.h"
+
+class ThrobAnimation;
+
+namespace views {
+
+// A button with custom rendering. The common base class of IconButton and
+// TextButton.
+class CustomButton : public Button,
+ public AnimationDelegate {
+ public:
+ virtual ~CustomButton();
+
+ // Possible states
+ enum ButtonState {
+ BS_NORMAL = 0,
+ BS_HOT,
+ BS_PUSHED,
+ BS_DISABLED,
+ BS_COUNT
+ };
+
+ // Get/sets the current display state of the button.
+ ButtonState state() const { return state_; }
+ void SetState(ButtonState state);
+
+ // Starts throbbing. See HoverAnimation for a description of cycles_til_stop.
+ void StartThrobbing(int cycles_til_stop);
+
+ // Set how long the hover animation will last for.
+ void SetAnimationDuration(int duration);
+
+ // Overridden from View:
+ virtual void SetEnabled(bool enabled);
+ virtual bool IsEnabled() const;
+ virtual bool IsFocusable() const;
+
+ void set_triggerable_event_flags(int triggerable_event_flags) {
+ triggerable_event_flags_ = triggerable_event_flags;
+ }
+
+ int triggerable_event_flags() const {
+ return triggerable_event_flags_;
+ }
+
+ protected:
+ // Construct the Button with a Listener. See comment for Button's ctor.
+ explicit CustomButton(ButtonListener* listener);
+
+ // 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 MouseEvent& e);
+
+ // Overridden from View:
+ virtual bool AcceleratorPressed(const Accelerator& accelerator);
+ virtual bool OnMousePressed(const MouseEvent& e);
+ virtual bool OnMouseDragged(const MouseEvent& e);
+ virtual void OnMouseReleased(const MouseEvent& e, bool canceled);
+ virtual void OnMouseEntered(const MouseEvent& e);
+ virtual void OnMouseMoved(const MouseEvent& e);
+ virtual void OnMouseExited(const MouseEvent& e);
+ virtual bool OnKeyPressed(const KeyEvent& e);
+ virtual bool OnKeyReleased(const KeyEvent& e);
+ virtual void OnDragDone();
+ virtual void ShowContextMenu(int x, int y, bool is_mouse_gesture);
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child);
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation);
+
+ // The button state (defined in implementation)
+ ButtonState state_;
+
+ // Hover animation.
+ scoped_ptr<ThrobAnimation> hover_animation_;
+
+ private:
+ // Set / test whether the button is highlighted (in the hover state).
+ void SetHighlighted(bool highlighted);
+ bool IsHighlighted() const;
+
+ // Returns whether the button is pushed.
+ bool IsPushed() const;
+
+ // Should we animate when the state changes? Defaults to true, but false while
+ // throbbing.
+ bool animate_on_state_change_;
+
+ // Mouse event flags which can trigger button actions.
+ int triggerable_event_flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomButton);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_
diff --git a/views/controls/button/image_button.cc b/views/controls/button/image_button.cc
new file mode 100644
index 0000000..3c4c7fe
--- /dev/null
+++ b/views/controls/button/image_button.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/image_button.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/throb_animation.h"
+#include "skia/ext/image_operations.h"
+
+namespace views {
+
+static const int kDefaultWidth = 16; // Default button width if no theme.
+static const int kDefaultHeight = 14; // Default button height if no theme.
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageButton, public:
+
+ImageButton::ImageButton(ButtonListener* listener)
+ : CustomButton(listener),
+ 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);
+}
+
+ImageButton::~ImageButton() {
+}
+
+void ImageButton::SetImage(ButtonState aState, SkBitmap* anImage) {
+ images_[aState] = anImage ? *anImage : SkBitmap();
+}
+
+void ImageButton::SetImageAlignment(HorizontalAlignment h_align,
+ VerticalAlignment v_align) {
+ h_alignment_ = h_align;
+ v_alignment_ = v_align;
+ SchedulePaint();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageButton, View overrides:
+
+gfx::Size ImageButton::GetPreferredSize() {
+ if (!images_[BS_NORMAL].isNull())
+ return gfx::Size(images_[BS_NORMAL].width(), images_[BS_NORMAL].height());
+ return gfx::Size(kDefaultWidth, kDefaultHeight);
+}
+
+void ImageButton::Paint(ChromeCanvas* canvas) {
+ // Call the base class first to paint any background/borders.
+ View::Paint(canvas);
+
+ SkBitmap img = GetImageToPaint();
+
+ if (!img.isNull()) {
+ int x = 0, y = 0;
+
+ if (h_alignment_ == ALIGN_CENTER)
+ x = (width() - img.width()) / 2;
+ else if (h_alignment_ == ALIGN_RIGHT)
+ x = width() - img.width();
+
+ if (v_alignment_ == ALIGN_MIDDLE)
+ y = (height() - img.height()) / 2;
+ else if (v_alignment_ == ALIGN_BOTTOM)
+ y = height() - img.height();
+
+ canvas->DrawBitmapInt(img, x, y);
+ }
+ PaintFocusBorder(canvas);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageButton, protected:
+
+SkBitmap ImageButton::GetImageToPaint() {
+ SkBitmap img;
+
+ if (!images_[BS_HOT].isNull() && hover_animation_->IsAnimating()) {
+ img = skia::ImageOperations::CreateBlendedBitmap(images_[BS_NORMAL],
+ images_[BS_HOT], hover_animation_->GetCurrentValue());
+ } else {
+ img = images_[state_];
+ }
+
+ return !img.isNull() ? img : images_[BS_NORMAL];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ToggleImageButton, public:
+
+ToggleImageButton::ToggleImageButton(ButtonListener* listener)
+ : ImageButton(listener),
+ toggled_(false) {
+}
+
+ToggleImageButton::~ToggleImageButton() {
+}
+
+void ToggleImageButton::SetToggled(bool toggled) {
+ if (toggled == toggled_)
+ return;
+
+ for (int i = 0; i < BS_COUNT; ++i) {
+ SkBitmap temp = images_[i];
+ images_[i] = alternate_images_[i];
+ alternate_images_[i] = temp;
+ }
+ toggled_ = toggled;
+ SchedulePaint();
+}
+
+void ToggleImageButton::SetToggledImage(ButtonState state, SkBitmap* image) {
+ if (toggled_) {
+ images_[state] = image ? *image : SkBitmap();
+ if (state_ == state)
+ SchedulePaint();
+ } else {
+ alternate_images_[state] = image ? *image : SkBitmap();
+ }
+}
+
+void ToggleImageButton::SetToggledTooltipText(const std::wstring& tooltip) {
+ toggled_tooltip_text_.assign(tooltip);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ToggleImageButton, ImageButton overrides:
+
+void ToggleImageButton::SetImage(ButtonState state, SkBitmap* image) {
+ if (toggled_) {
+ alternate_images_[state] = image ? *image : SkBitmap();
+ } else {
+ images_[state] = image ? *image : SkBitmap();
+ if (state_ == state)
+ SchedulePaint();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ToggleImageButton, View overrides:
+
+bool ToggleImageButton::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;
+}
+
+} // namespace views
diff --git a/views/controls/button/image_button.h b/views/controls/button/image_button.h
new file mode 100644
index 0000000..c687561
--- /dev/null
+++ b/views/controls/button/image_button.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_
+
+#include "skia/include/SkBitmap.h"
+#include "views/controls/button/custom_button.h"
+
+namespace views {
+
+// An image button.
+class ImageButton : public CustomButton {
+ public:
+ explicit ImageButton(ButtonListener* listener);
+ virtual ~ImageButton();
+
+ // 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 };
+
+ // Sets how the image is laid out within the button's bounds.
+ void SetImageAlignment(HorizontalAlignment h_align,
+ VerticalAlignment v_align);
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Paint(ChromeCanvas* canvas);
+
+ protected:
+ // Returns the image to paint. This is invoked from paint and returns a value
+ // from images.
+ virtual SkBitmap GetImageToPaint();
+
+ // The images used to render the different states of this button.
+ SkBitmap images_[BS_COUNT];
+
+ private:
+ // Image alignment.
+ HorizontalAlignment h_alignment_;
+ VerticalAlignment v_alignment_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageButton);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ToggleImageButton
+//
+// A toggle-able ImageButton. It swaps out its graphics when toggled.
+//
+////////////////////////////////////////////////////////////////////////////////
+class ToggleImageButton : public ImageButton {
+ public:
+ ToggleImageButton(ButtonListener* listener);
+ virtual ~ToggleImageButton();
+
+ // Change the toggled state.
+ void SetToggled(bool toggled);
+
+ // 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);
+
+ // Set the tooltip text displayed when the button is toggled.
+ void SetToggledTooltipText(const std::wstring& tooltip);
+
+ // Overridden from ImageButton:
+ virtual void SetImage(ButtonState aState, SkBitmap* anImage);
+
+ // Overridden from View:
+ virtual bool GetTooltipText(int x, int y, std::wstring* tooltip);
+
+ 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_[BS_COUNT];
+
+ // True if the button is currently toggled.
+ 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(ToggleImageButton);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_
diff --git a/views/controls/button/menu_button.cc b/views/controls/button/menu_button.cc
new file mode 100644
index 0000000..888137b
--- /dev/null
+++ b/views/controls/button/menu_button.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/menu_button.h"
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "chrome/common/win_util.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "views/controls/button/button.h"
+#include "views/controls/menu/view_menu_delegate.h"
+#include "views/event.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace views {
+
+// 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(ButtonListener* listener,
+ const std::wstring& text,
+ ViewMenuDelegate* menu_delegate,
+ bool show_menu_marker)
+ : TextButton(listener, text),
+ menu_visible_(false),
+ menu_closed_time_(),
+ menu_delegate_(menu_delegate),
+ show_menu_marker_(show_menu_marker) {
+ if (kMenuMarker == NULL) {
+ kMenuMarker = ResourceBundle::GetSharedInstance()
+ .GetBitmapNamed(IDR_MENU_DROPARROW);
+ }
+ set_alignment(TextButton::ALIGN_LEFT);
+}
+
+MenuButton::~MenuButton() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuButton - Public APIs
+//
+////////////////////////////////////////////////////////////////////////////////
+
+gfx::Size MenuButton::GetPreferredSize() {
+ gfx::Size prefsize = TextButton::GetPreferredSize();
+ if (show_menu_marker_) {
+ prefsize.Enlarge(kMenuMarker->width() + kMenuMarkerPaddingLeft +
+ kMenuMarkerPaddingRight,
+ 0);
+ }
+ return prefsize;
+}
+
+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 views' 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(width() - insets.right() -
+ kMenuMarker->width() - kMenuMarkerPaddingRight,
+ height() / 2 - kMenuMarker->height() / 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() {
+ Widget* widget = GetWidget();
+
+ if (!widget) {
+ NOTREACHED();
+ return 0;
+ }
+
+ HWND hwnd = widget->GetNativeView();
+ 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_) {
+ gfx::Rect lb = GetLocalBounds(true);
+
+ // The position of the menu depends on whether or not the locale is
+ // right-to-left.
+ gfx::Point menu_position(lb.right(), lb.bottom());
+ if (UILayoutIsRightToLeft())
+ menu_position.set_x(lb.x());
+
+ 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.set_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.ToPOINT(),
+ GetWidget()->GetNativeView());
+ 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);
+
+ // 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 MouseEvent& e) {
+ RequestFocus();
+ if (state() != 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.location()) &&
+ GetDragOperations(e.x(), e.y()) == 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 MouseEvent& e,
+ bool canceled) {
+ if (GetDragOperations(e.x(), e.y()) != DragDropTypes::DRAG_NONE &&
+ state() != BS_DISABLED && !canceled && !InDrag() &&
+ e.IsOnlyLeftMouseButton() && HitTest(e.location())) {
+ 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() == VK_SPACE) || (e.GetCharacter() == VK_RETURN)) {
+ return Activate();
+ }
+ 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) {
+ if ((state_ != BS_DISABLED) && (!menu_visible_) && (!InDrag())) {
+ SetState(BS_NORMAL);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuButton - accessibility
+//
+////////////////////////////////////////////////////////////////////////////////
+
+bool MenuButton::GetAccessibleDefaultAction(std::wstring* action) {
+ DCHECK(action);
+
+ action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS));
+ return true;
+}
+
+bool MenuButton::GetAccessibleRole(AccessibilityTypes::Role* role) {
+ DCHECK(role);
+
+ *role = AccessibilityTypes::ROLE_BUTTONDROPDOWN;
+ return true;
+}
+
+bool MenuButton::GetAccessibleState(AccessibilityTypes::State* state) {
+ DCHECK(state);
+
+ *state = AccessibilityTypes::STATE_HASPOPUP;
+ return true;
+}
+
+} // namespace views
diff --git a/views/controls/button/menu_button.h b/views/controls/button/menu_button.h
new file mode 100644
index 0000000..cbe9ab8
--- /dev/null
+++ b/views/controls/button/menu_button.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
+
+#include <windows.h>
+
+#include "app/gfx/chrome_font.h"
+#include "base/time.h"
+#include "views/background.h"
+#include "views/controls/button/text_button.h"
+
+namespace views {
+
+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(ButtonListener* listener,
+ const std::wstring& text,
+ ViewMenuDelegate* menu_delegate,
+ bool show_menu_marker);
+ virtual ~MenuButton();
+
+ void set_menu_delegate(ViewMenuDelegate* delegate) {
+ menu_delegate_ = delegate;
+ }
+
+ // Activate the button (called when the button is pressed).
+ virtual bool Activate();
+
+ // Overridden to take into account the potential use of a drop marker.
+ virtual gfx::Size GetPreferredSize();
+ virtual void Paint(ChromeCanvas* canvas, bool for_drag);
+
+ // These methods are overriden to implement a simple push button
+ // behavior
+ virtual bool OnMousePressed(const MouseEvent& e);
+ void OnMouseReleased(const MouseEvent& e, bool canceled);
+ virtual bool OnKeyReleased(const KeyEvent& e);
+ virtual void OnMouseExited(const MouseEvent& event);
+
+ // Accessibility accessors, overridden from View.
+ virtual bool GetAccessibleDefaultAction(std::wstring* action);
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role);
+ virtual bool GetAccessibleState(AccessibilityTypes::State* 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.
+ base::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 views
+
+#endif // VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
diff --git a/views/controls/button/native_button.cc b/views/controls/button/native_button.cc
new file mode 100644
index 0000000..0af6f88
--- /dev/null
+++ b/views/controls/button/native_button.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "views/controls/button/native_button.h"
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+
+namespace views {
+
+static int kButtonBorderHWidth = 8;
+
+// static
+const char NativeButton::kViewClassName[] = "views/NativeButton";
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButton, public:
+
+NativeButton::NativeButton(ButtonListener* listener)
+ : Button(listener),
+ native_wrapper_(NULL),
+ is_default_(false),
+ ignore_minimum_size_(false),
+ minimum_size_(50, 14) {
+ // The min size in DLUs comes from
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp
+ InitBorder();
+ SetFocusable(true);
+}
+
+NativeButton::NativeButton(ButtonListener* listener, const std::wstring& label)
+ : Button(listener),
+ native_wrapper_(NULL),
+ is_default_(false),
+ ignore_minimum_size_(false),
+ minimum_size_(50, 14) {
+ SetLabel(label); // SetLabel takes care of label layout in RTL UI.
+ // The min size in DLUs comes from
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp
+ InitBorder();
+ SetFocusable(true);
+}
+
+NativeButton::~NativeButton() {
+}
+
+void NativeButton::SetLabel(const std::wstring& label) {
+ label_ = label;
+
+ // 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(label_, &localized_label))
+ label_ = localized_label;
+
+ if (native_wrapper_)
+ native_wrapper_->UpdateLabel();
+}
+
+void NativeButton::SetIsDefault(bool is_default) {
+ if (is_default == is_default_)
+ return;
+ if (is_default)
+ AddAccelerator(Accelerator(VK_RETURN, false, false, false));
+ else
+ RemoveAccelerator(Accelerator(VK_RETURN, false, false, false));
+ SetAppearsAsDefault(is_default);
+}
+
+void NativeButton::SetAppearsAsDefault(bool appears_as_default) {
+ is_default_ = appears_as_default;
+ if (native_wrapper_)
+ native_wrapper_->UpdateDefault();
+}
+
+void NativeButton::ButtonPressed() {
+ RequestFocus();
+
+ // TODO(beng): obtain mouse event flags for native buttons someday.
+ NotifyClick(mouse_event_flags());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButton, View overrides:
+
+gfx::Size NativeButton::GetPreferredSize() {
+ if (!native_wrapper_)
+ return gfx::Size();
+
+ gfx::Size sz = native_wrapper_->GetView()->GetPreferredSize();
+
+ // Add in the border size. (Do this before clamping the minimum size in case
+ // that clamping causes an increase in size that would include the borders.
+ gfx::Insets border = GetInsets();
+ sz.set_width(sz.width() + border.left() + border.right());
+ sz.set_height(sz.height() + border.top() + border.bottom());
+
+ // Clamp the size returned to at least the minimum size.
+ if (!ignore_minimum_size_) {
+ if (minimum_size_.width()) {
+ int min_width = font_.horizontal_dlus_to_pixels(minimum_size_.width());
+ sz.set_width(std::max(static_cast<int>(sz.width()), min_width));
+ }
+ if (minimum_size_.height()) {
+ int min_height = font_.vertical_dlus_to_pixels(minimum_size_.height());
+ sz.set_height(std::max(static_cast<int>(sz.height()), min_height));
+ }
+ }
+
+ return sz;
+}
+
+void NativeButton::Layout() {
+ if (native_wrapper_) {
+ native_wrapper_->GetView()->SetBounds(0, 0, width(), height());
+ native_wrapper_->GetView()->Layout();
+ }
+}
+
+void NativeButton::SetEnabled(bool flag) {
+ Button::SetEnabled(flag);
+ if (native_wrapper_)
+ native_wrapper_->UpdateEnabled();
+}
+
+void NativeButton::ViewHierarchyChanged(bool is_add, View* parent,
+ View* child) {
+ if (is_add && !native_wrapper_ && GetWidget()) {
+ CreateWrapper();
+ AddChildView(native_wrapper_->GetView());
+ }
+}
+
+std::string NativeButton::GetClassName() const {
+ return kViewClassName;
+}
+
+bool NativeButton::AcceleratorPressed(const Accelerator& accelerator) {
+ if (IsEnabled()) {
+ NotifyClick(mouse_event_flags());
+ return true;
+ }
+ return false;
+}
+
+void NativeButton::Focus() {
+ // Forward the focus to the wrapper.
+ if (native_wrapper_)
+ native_wrapper_->SetFocus();
+ else
+ Button::Focus(); // Will focus the RootView window (so we still get
+ // keyboard messages).
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButton, protected:
+
+void NativeButton::CreateWrapper() {
+ native_wrapper_ = NativeButtonWrapper::CreateNativeButtonWrapper(this);
+ native_wrapper_->UpdateLabel();
+ native_wrapper_->UpdateEnabled();
+}
+
+void NativeButton::InitBorder() {
+ set_border(Border::CreateEmptyBorder(0, kButtonBorderHWidth, 0,
+ kButtonBorderHWidth));
+}
+
+} // namespace views
diff --git a/views/controls/button/native_button.h b/views/controls/button/native_button.h
new file mode 100644
index 0000000..52946d7
--- /dev/null
+++ b/views/controls/button/native_button.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_
+
+#include "app/gfx/chrome_font.h"
+#include "views/controls/button/button.h"
+#include "views/controls/button/native_button_wrapper.h"
+
+class ChromeFont;
+
+namespace views {
+
+class NativeButton : public Button {
+ public:
+ // The button's class name.
+ static const char kViewClassName[];
+
+ explicit NativeButton(ButtonListener* listener);
+ NativeButton(ButtonListener* listener, const std::wstring& label);
+ virtual ~NativeButton();
+
+ // Sets/Gets the text to be used as the button's label.
+ void SetLabel(const std::wstring& label);
+ std::wstring label() const { return label_; }
+
+ // Sets the font to be used when displaying the button's label.
+ void set_font(const ChromeFont& font) { font_ = font; }
+ const ChromeFont& font() const { return font_; }
+
+ // Sets/Gets whether or not the button appears and behaves as the default
+ // button in its current context.
+ void SetIsDefault(bool default_button);
+ bool is_default() const { return is_default_; }
+
+ // Sets whether or not the button appears as the default button. This does
+ // not make it behave as the default (i.e. no enter key accelerator is
+ // registered, use SetIsDefault for that).
+ void SetAppearsAsDefault(bool default_button);
+
+ void set_minimum_size(const gfx::Size& minimum_size) {
+ minimum_size_ = minimum_size;
+ }
+ void set_ignore_minimum_size(bool ignore_minimum_size) {
+ ignore_minimum_size_ = ignore_minimum_size;
+ }
+
+ // Called by the wrapper when the actual wrapped native button was pressed.
+ void ButtonPressed();
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Layout();
+ virtual void SetEnabled(bool flag);
+ virtual void Focus();
+
+ protected:
+ virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child);
+ virtual std::string GetClassName() const;
+ virtual bool AcceleratorPressed(const Accelerator& accelerator);
+
+ // Create the button wrapper. Can be overridden by subclass to create a
+ // wrapper of a particular type. See NativeButtonWrapper interface for types.
+ virtual void CreateWrapper();
+
+ // Sets a border to the button. Override to set a different border or to not
+ // set one (the default is 0,8,0,8 for push buttons).
+ virtual void InitBorder();
+
+ // The object that actually implements the native button.
+ NativeButtonWrapper* native_wrapper_;
+
+ private:
+ // The button label.
+ std::wstring label_;
+
+ // True if the button is the default button in its context.
+ bool is_default_;
+
+ // The font used to render the button label.
+ ChromeFont font_;
+
+ // True if the button should ignore the minimum size for the platform. Default
+ // is false. Set to true to create narrower buttons.
+ bool ignore_minimum_size_;
+
+ // The minimum size of the button from the specified size in native dialog
+ // units. The definition of this unit may vary from platform to platform. If
+ // the width/height is non-zero, the preferred size of the button will not be
+ // less than this value when the dialog units are converted to pixels.
+ gfx::Size minimum_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeButton);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_
diff --git a/views/controls/button/native_button_win.cc b/views/controls/button/native_button_win.cc
new file mode 100644
index 0000000..6748241
--- /dev/null
+++ b/views/controls/button/native_button_win.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "views/controls/button/native_button_win.h"
+
+#include "base/logging.h"
+#include "views/controls/button/checkbox.h"
+#include "views/controls/button/native_button.h"
+#include "views/controls/button/radio_button.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButtonWin, public:
+
+NativeButtonWin::NativeButtonWin(NativeButton* native_button)
+ : NativeControlWin(),
+ native_button_(native_button) {
+ // Associates the actual HWND with the native_button so the native_button is
+ // the one considered as having the focus (not the wrapper) when the HWND is
+ // focused directly (with a click for example).
+ SetAssociatedFocusView(native_button);
+}
+
+NativeButtonWin::~NativeButtonWin() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButtonWin, NativeButtonWrapper implementation:
+
+void NativeButtonWin::UpdateLabel() {
+ SetWindowText(GetHWND(), native_button_->label().c_str());
+}
+
+void NativeButtonWin::UpdateFont() {
+ SendMessage(GetHWND(), WM_SETFONT,
+ reinterpret_cast<WPARAM>(native_button_->font().hfont()),
+ FALSE);
+}
+
+void NativeButtonWin::UpdateEnabled() {
+ SetEnabled(native_button_->IsEnabled());
+}
+
+void NativeButtonWin::UpdateDefault() {
+ if (!IsCheckbox()) {
+ SendMessage(GetHWND(), BM_SETSTYLE,
+ native_button_->is_default() ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON,
+ true);
+ }
+}
+
+View* NativeButtonWin::GetView() {
+ return this;
+}
+
+void NativeButtonWin::SetFocus() {
+ // Focus the associated HWND.
+ Focus();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButtonWin, View overrides:
+
+gfx::Size NativeButtonWin::GetPreferredSize() {
+ SIZE sz = {0};
+ SendMessage(GetHWND(), BCM_GETIDEALSIZE, 0, reinterpret_cast<LPARAM>(&sz));
+
+ return gfx::Size(sz.cx, sz.cy);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButtonWin, NativeControlWin overrides:
+
+bool NativeButtonWin::ProcessMessage(UINT message, WPARAM w_param,
+ LPARAM l_param, LRESULT* result) {
+ if (message == WM_COMMAND && HIWORD(w_param) == BN_CLICKED) {
+ native_button_->ButtonPressed();
+ *result = 0;
+ return true;
+ }
+ return NativeControlWin::ProcessMessage(message, w_param, l_param, result);
+}
+
+bool NativeButtonWin::OnKeyDown(int vkey) {
+ bool enter_pressed = vkey == VK_RETURN;
+ if (enter_pressed)
+ native_button_->ButtonPressed();
+ return enter_pressed;
+}
+
+bool NativeButtonWin::NotifyOnKeyDown() const {
+ return true;
+}
+
+void NativeButtonWin::CreateNativeControl() {
+ DWORD flags = WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_PUSHBUTTON;
+ if (native_button_->is_default())
+ flags |= BS_DEFPUSHBUTTON;
+ HWND control_hwnd = CreateWindowEx(GetAdditionalExStyle(), L"BUTTON", L"",
+ flags, 0, 0, width(), height(),
+ GetWidget()->GetNativeView(), NULL, NULL,
+ NULL);
+ NativeControlCreated(control_hwnd);
+}
+
+void NativeButtonWin::NativeControlCreated(HWND control_hwnd) {
+ NativeControlWin::NativeControlCreated(control_hwnd);
+
+ UpdateFont();
+ UpdateLabel();
+ UpdateDefault();
+}
+
+// We could obtain this from the theme, but that only works if themes are
+// active.
+static const int kCheckboxSize = 13; // pixels
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeCheckboxWin, public:
+
+NativeCheckboxWin::NativeCheckboxWin(Checkbox* checkbox)
+ : NativeButtonWin(checkbox),
+ checkbox_(checkbox) {
+}
+
+NativeCheckboxWin::~NativeCheckboxWin() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeCheckboxWin, View overrides:
+
+gfx::Size NativeCheckboxWin::GetPreferredSize() {
+ return gfx::Size(kCheckboxSize, kCheckboxSize);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeCheckboxWin, NativeButtonWrapper implementation:
+
+void NativeCheckboxWin::UpdateChecked() {
+ SendMessage(GetHWND(), BM_SETCHECK,
+ checkbox_->checked() ? BST_CHECKED : BST_UNCHECKED, 0);
+}
+
+void NativeCheckboxWin::SetPushed(bool pushed) {
+ SendMessage(GetHWND(), BM_SETSTATE, pushed, 0);
+}
+
+bool NativeCheckboxWin::OnKeyDown(int vkey) {
+ // Override the NativeButtonWin behavior which triggers the button on enter
+ // key presses when focused.
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeCheckboxWin, NativeButtonWin overrides:
+
+bool NativeCheckboxWin::ProcessMessage(UINT message, WPARAM w_param,
+ LPARAM l_param, LRESULT* result) {
+ if (message == WM_COMMAND && HIWORD(w_param) == BN_CLICKED) {
+ if (!IsRadioButton() || !checkbox_->checked())
+ checkbox_->SetChecked(!checkbox_->checked());
+ // Fall through to the NativeButtonWin's handler, which will send the
+ // clicked notification to the listener...
+ }
+ return NativeButtonWin::ProcessMessage(message, w_param, l_param, result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeCheckboxWin, protected:
+
+void NativeCheckboxWin::CreateNativeControl() {
+ HWND control_hwnd = CreateWindowEx(
+ WS_EX_TRANSPARENT | GetAdditionalExStyle(), L"BUTTON", L"",
+ WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_CHECKBOX,
+ 0, 0, width(), height(), GetWidget()->GetNativeView(), NULL, NULL, NULL);
+ NativeControlCreated(control_hwnd);
+}
+
+void NativeCheckboxWin::NativeControlCreated(HWND control_hwnd) {
+ NativeButtonWin::NativeControlCreated(control_hwnd);
+ UpdateChecked();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeRadioButtonWin, public:
+
+NativeRadioButtonWin::NativeRadioButtonWin(RadioButton* radio_button)
+ : NativeCheckboxWin(radio_button) {
+}
+
+NativeRadioButtonWin::~NativeRadioButtonWin() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeRadioButtonWin, NativeCheckboxWin overrides:
+
+void NativeRadioButtonWin::CreateNativeControl() {
+ HWND control_hwnd = CreateWindowEx(
+ GetAdditionalExStyle(), L"BUTTON",
+ L"", WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_RADIOBUTTON,
+ 0, 0, width(), height(), GetWidget()->GetNativeView(), NULL, NULL, NULL);
+ NativeControlCreated(control_hwnd);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeButtonWrapper, public:
+
+// static
+int NativeButtonWrapper::GetFixedWidth() {
+ return kCheckboxSize;
+}
+
+// static
+NativeButtonWrapper* NativeButtonWrapper::CreateNativeButtonWrapper(
+ NativeButton* native_button) {
+ return new NativeButtonWin(native_button);
+}
+
+// static
+NativeButtonWrapper* NativeButtonWrapper::CreateCheckboxWrapper(
+ Checkbox* checkbox) {
+ return new NativeCheckboxWin(checkbox);
+}
+
+// static
+NativeButtonWrapper* NativeButtonWrapper::CreateRadioButtonWrapper(
+ RadioButton* radio_button) {
+ return new NativeRadioButtonWin(radio_button);
+}
+
+} // namespace views
diff --git a/views/controls/button/native_button_win.h b/views/controls/button/native_button_win.h
new file mode 100644
index 0000000..3f5141a
--- /dev/null
+++ b/views/controls/button/native_button_win.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_
+#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_
+
+#include "views/controls/native_control_win.h"
+#include "views/controls/button/native_button_wrapper.h"
+
+namespace views {
+
+// A View that hosts a native Windows button.
+class NativeButtonWin : public NativeControlWin,
+ public NativeButtonWrapper {
+ public:
+ explicit NativeButtonWin(NativeButton* native_button);
+ virtual ~NativeButtonWin();
+
+ // Overridden from NativeButtonWrapper:
+ virtual void UpdateLabel();
+ virtual void UpdateFont();
+ virtual void UpdateEnabled();
+ virtual void UpdateDefault();
+ virtual View* GetView();
+ virtual void SetFocus();
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+
+ // Overridden from NativeControlWin:
+ virtual bool ProcessMessage(UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* result);
+ virtual bool OnKeyDown(int vkey);
+
+ protected:
+ virtual bool NotifyOnKeyDown() const;
+ virtual void CreateNativeControl();
+ virtual void NativeControlCreated(HWND control_hwnd);
+
+ // Returns true if this button is actually a checkbox or radio button.
+ virtual bool IsCheckbox() const { return false; }
+
+ private:
+ // The NativeButton we are bound to.
+ NativeButton* native_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeButtonWin);
+};
+
+// A View that hosts a native Windows checkbox.
+class NativeCheckboxWin : public NativeButtonWin {
+ public:
+ explicit NativeCheckboxWin(Checkbox* native_button);
+ virtual ~NativeCheckboxWin();
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+
+ // Overridden from NativeButtonWrapper:
+ virtual void UpdateChecked();
+ virtual void SetPushed(bool pushed);
+ virtual bool OnKeyDown(int vkey);
+
+ // Overridden from NativeControlWin:
+ virtual bool ProcessMessage(UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* result);
+
+ protected:
+ virtual void CreateNativeControl();
+ virtual void NativeControlCreated(HWND control_hwnd);
+ virtual bool IsCheckbox() const { return true; }
+
+ // Returns true if this button is actually a radio button.
+ virtual bool IsRadioButton() const { return false; }
+
+ private:
+ // The Checkbox we are bound to.
+ Checkbox* checkbox_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeCheckboxWin);
+};
+
+// A View that hosts a native Windows radio button.
+class NativeRadioButtonWin : public NativeCheckboxWin {
+ public:
+ explicit NativeRadioButtonWin(RadioButton* radio_button);
+ virtual ~NativeRadioButtonWin();
+
+ protected:
+ // Overridden from NativeCheckboxWin:
+ virtual void CreateNativeControl();
+ virtual bool IsRadioButton() const { return true; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NativeRadioButtonWin);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_
diff --git a/views/controls/button/native_button_wrapper.h b/views/controls/button/native_button_wrapper.h
new file mode 100644
index 0000000..5535ed4
--- /dev/null
+++ b/views/controls/button/native_button_wrapper.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_
+#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_
+
+class ChromeFont;
+
+namespace views {
+
+class Checkbox;
+class NativeButton;
+class RadioButton;
+
+// A specialization of NativeControlWrapper that hosts a platform-native button.
+class NativeButtonWrapper {
+ public:
+ // Updates the native button's label from the state stored in its associated
+ // NativeButton.
+ virtual void UpdateLabel() = 0;
+
+ // Updates the native button's label font from the state stored in its
+ // associated NativeButton.
+ virtual void UpdateFont() = 0;
+
+ // Updates the native button's enabled state from the state stored in its
+ // associated NativeButton.
+ virtual void UpdateEnabled() = 0;
+
+ // Updates the native button's default state from the state stored in its
+ // associated NativeButton.
+ virtual void UpdateDefault() = 0;
+
+ // Updates the native button's checked state from the state stored in its
+ // associated NativeCheckbox. Valid only for checkboxes and radio buttons.
+ virtual void UpdateChecked() {}
+
+ // Shows the pushed state for the button if |pushed| is true.
+ virtual void SetPushed(bool pushed) {};
+
+ // Retrieves the views::View that hosts the native control.
+ virtual View* GetView() = 0;
+
+ // Sets the focus to the button.
+ virtual void SetFocus() = 0;
+
+ // Return the width of the button. Used for fixed size buttons (checkboxes and
+ // radio buttons) only.
+ static int GetFixedWidth();
+
+ // Creates an appropriate NativeButtonWrapper for the platform.
+ static NativeButtonWrapper* CreateNativeButtonWrapper(NativeButton* button);
+ static NativeButtonWrapper* CreateCheckboxWrapper(Checkbox* checkbox);
+ static NativeButtonWrapper* CreateRadioButtonWrapper(
+ RadioButton* radio_button);
+
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_
diff --git a/views/controls/button/radio_button.cc b/views/controls/button/radio_button.cc
new file mode 100644
index 0000000..3f4820d
--- /dev/null
+++ b/views/controls/button/radio_button.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "views/controls/button/radio_button.h"
+
+#include "views/widget/root_view.h"
+
+namespace views {
+
+// static
+const char RadioButton::kViewClassName[] = "views/RadioButton";
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButton, public:
+
+RadioButton::RadioButton() : Checkbox() {
+}
+
+RadioButton::RadioButton(const std::wstring& label) : Checkbox(label) {
+}
+
+RadioButton::RadioButton(const std::wstring& label, int group_id)
+ : Checkbox(label) {
+ SetGroup(group_id);
+}
+
+RadioButton::~RadioButton() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButton, Checkbox overrides:
+
+void RadioButton::SetChecked(bool checked) {
+ if (checked == RadioButton::checked())
+ return;
+ if (checked) {
+ // 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->SetChecked(false);
+ }
+ }
+ }
+ }
+ Checkbox::SetChecked(checked);
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButton, View overrides:
+
+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->checked())
+ return radio_button;
+ }
+ return NULL;
+}
+
+bool RadioButton::IsGroupFocusTraversable() const {
+ // When focusing a radio button with tab/shift+tab, only the selected button
+ // from the group should be focused.
+ return false;
+}
+
+void RadioButton::OnMouseReleased(const MouseEvent& event, bool canceled) {
+ native_wrapper_->SetPushed(false);
+ // Set the checked state to true only if we are unchecked, since we can't
+ // be toggled on and off like a checkbox.
+ if (!checked() && !canceled && HitTestLabel(event))
+ SetChecked(true);
+
+ ButtonPressed();
+}
+
+std::string RadioButton::GetClassName() const {
+ return kViewClassName;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButton, NativeButton overrides:
+
+void RadioButton::CreateWrapper() {
+ native_wrapper_ = NativeButtonWrapper::CreateRadioButtonWrapper(this);
+ native_wrapper_->UpdateLabel();
+ native_wrapper_->UpdateChecked();
+}
+
+} // namespace views
diff --git a/views/controls/button/radio_button.h b/views/controls/button/radio_button.h
new file mode 100644
index 0000000..ab7fbe2
--- /dev/null
+++ b/views/controls/button/radio_button.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_
+
+#include "views/controls/button/checkbox.h"
+
+namespace views {
+
+// A Checkbox subclass representing a radio button.
+class RadioButton : public Checkbox {
+ public:
+ // The button's class name.
+ static const char kViewClassName[];
+
+ RadioButton();
+ RadioButton(const std::wstring& label);
+ RadioButton(const std::wstring& label, int group_id);
+ virtual ~RadioButton();
+
+ // Overridden from Checkbox:
+ virtual void SetChecked(bool checked);
+
+ // Overridden from View:
+ virtual View* GetSelectedViewForGroup(int group_id);
+ virtual bool IsGroupFocusTraversable() const;
+ virtual void OnMouseReleased(const MouseEvent& event, bool canceled);
+
+ protected:
+ virtual std::string GetClassName() const;
+
+ // Overridden from NativeButton:
+ virtual void CreateWrapper();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RadioButton);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_
diff --git a/views/controls/button/text_button.cc b/views/controls/button/text_button.cc
new file mode 100644
index 0000000..4f68b90
--- /dev/null
+++ b/views/controls/button/text_button.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/button/text_button.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "app/throb_animation.h"
+#include "app/resource_bundle.h"
+#include "views/controls/button/button.h"
+#include "views/event.h"
+#include "grit/theme_resources.h"
+
+namespace views {
+
+// 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->state();
+
+ // TextButton takes care of deciding when to call Paint.
+ const MBBImageSet* set = &hot_set_;
+ if (state == TextButton::BS_PUSHED)
+ set = &pushed_set_;
+
+ if (set) {
+ gfx::Rect bounds = view.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, public:
+
+TextButton::TextButton(ButtonListener* listener, const std::wstring& text)
+ : CustomButton(listener),
+ alignment_(ALIGN_LEFT),
+ font_(ResourceBundle::GetSharedInstance().GetFont(
+ ResourceBundle::BaseFont)),
+ color_(kEnabledColor),
+ max_width_(0) {
+ SetText(text);
+ set_border(new TextButtonBorder);
+ SetAnimationDuration(kHoverAnimationDurationMs);
+}
+
+TextButton::~TextButton() {
+}
+
+void TextButton::SetText(const std::wstring& text) {
+ text_ = text;
+ // Update our new current and max text size
+ text_size_.SetSize(font_.GetStringWidth(text_), font_.height());
+ max_text_size_.SetSize(std::max(max_text_size_.width(), text_size_.width()),
+ std::max(max_text_size_.height(),
+ text_size_.height()));
+}
+
+void TextButton::SetIcon(const SkBitmap& icon) {
+ icon_ = icon;
+}
+
+void TextButton::ClearMaxTextSize() {
+ max_text_size_ = text_size_;
+}
+
+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 = width() - insets.width();
+ int available_height = height() - insets.height();
+ // Use the actual text (not max) size to properly center the text.
+ int content_width = text_size_.width();
+ 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(text_size_.width(),
+ width() - insets.right() - text_x);
+ int text_y = (available_height - text_size_.height()) / 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_.height());
+ text_bounds.set_x(MirroredLeftPointForRect(text_bounds));
+
+ if (for_drag) {
+#if defined(OS_WIN)
+ // TODO(erg): Either port DrawStringWithHalo to linux or find an
+ // alternative here.
+ canvas->DrawStringWithHalo(text_, font_, color_, kHighlightColor,
+ text_bounds.x(),
+ text_bounds.y(),
+ text_bounds.width(),
+ text_bounds.height(),
+ l10n_util::DefaultCanvasTextAlignment());
+#endif
+ } else {
+ // 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());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextButton, View overrides:
+
+gfx::Size TextButton::GetPreferredSize() {
+ gfx::Insets insets = GetInsets();
+
+ // Use the max size to set the button boundaries.
+ gfx::Size prefsize(max_text_size_.width() + icon_.width() + insets.width(),
+ std::max(max_text_size_.height(), icon_.height()) +
+ insets.height());
+
+ if (icon_.width() > 0 && !text_.empty())
+ prefsize.Enlarge(kIconTextPadding, 0);
+
+ if (max_width_ > 0)
+ prefsize.set_width(std::min(max_width_, prefsize.width()));
+
+ return prefsize;
+}
+
+gfx::Size TextButton::GetMinimumSize() {
+ return max_text_size_;
+}
+
+void TextButton::SetEnabled(bool enabled) {
+ if (enabled == IsEnabled())
+ return;
+ CustomButton::SetEnabled(enabled);
+ color_ = enabled ? kEnabledColor : kDisabledColor;
+ SchedulePaint();
+}
+
+bool TextButton::OnMousePressed(const MouseEvent& e) {
+ return true;
+}
+
+void TextButton::Paint(ChromeCanvas* canvas) {
+ Paint(canvas, false);
+}
+
+} // namespace views
diff --git a/views/controls/button/text_button.h b/views/controls/button/text_button.h
new file mode 100644
index 0000000..16bac57
--- /dev/null
+++ b/views/controls/button/text_button.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_
+#define VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_
+
+#include "app/gfx/chrome_font.h"
+#include "skia/include/SkBitmap.h"
+#include "views/border.h"
+#include "views/controls/button/custom_button.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 CustomButton {
+ public:
+ TextButton(ButtonListener* listener, const std::wstring& text);
+ virtual ~TextButton();
+
+ // 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);
+ std::wstring text() const { return text_; }
+
+ enum TextAlignment {
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT
+ };
+
+ void set_alignment(TextAlignment alignment) { alignment_ = alignment; }
+
+ // Sets the icon.
+ void SetIcon(const SkBitmap& icon);
+ SkBitmap icon() const { 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();
+
+ void set_max_width(int max_width) { max_width_ = max_width; }
+
+ // Paint the button into the specified canvas. If |for_drag| is true, the
+ // function paints a drag image representation into the canvas.
+ virtual void Paint(ChromeCanvas* canvas, bool for_drag);
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+ virtual gfx::Size GetMinimumSize();
+ virtual void SetEnabled(bool enabled);
+
+ protected:
+ virtual bool OnMousePressed(const MouseEvent& e);
+ virtual void Paint(ChromeCanvas* canvas);
+
+ private:
+ // The text string that is displayed in the button.
+ std::wstring text_;
+
+ // The size of the text string.
+ gfx::Size text_size_;
+
+ // Track the size of the largest text string seen so far, so that
+ // changing text_ will not resize the button boundary.
+ gfx::Size max_text_size_;
+
+ // The alignment of the text string within the button.
+ TextAlignment alignment_;
+
+ // The font used to paint the text.
+ ChromeFont font_;
+
+ // Text color.
+ SkColor color_;
+
+ // An icon displayed with the text.
+ SkBitmap icon_;
+
+ // The width of the button will never be larger than this value. A value <= 0
+ // indicates the width is not constrained.
+ int max_width_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TextButton);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_
diff --git a/views/controls/combo_box.cc b/views/controls/combo_box.cc
new file mode 100644
index 0000000..eb079f7
--- /dev/null
+++ b/views/controls/combo_box.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/combo_box.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/gfx/native_theme.h"
+#include "base/gfx/rect.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 views {
+
+ComboBox::ComboBox(Model* model)
+ : model_(model), selected_item_(0), listener_(NULL), content_width_(0) {
+}
+
+ComboBox::~ComboBox() {
+}
+
+void ComboBox::SetListener(Listener* listener) {
+ listener_ = listener;
+}
+
+gfx::Size ComboBox::GetPreferredSize() {
+ HWND hwnd = GetNativeControlHWND();
+ if (!hwnd)
+ return gfx::Size();
+
+ COMBOBOXINFO cbi;
+ memset(reinterpret_cast<unsigned char*>(&cbi), 0, sizeof(cbi));
+ cbi.cbSize = sizeof(cbi);
+ // Note: Don't use CB_GETCOMBOBOXINFO since that crashes on WOW64 systems
+ // when you have a global message hook installed.
+ GetComboBoxInfo(hwnd, &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.
+ int pref_width = 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.
+ int pref_height = std::max(rect_item.height() + 2 * kItemOffset,
+ rect_button.height() + 2 * border.height());
+ return gfx::Size(pref_width, pref_height);
+}
+
+// VK_ESCAPE should be handled by this view when the drop down list is active.
+// In other words, the list should be closed instead of the dialog.
+bool ComboBox::OverrideAccelerator(const Accelerator& accelerator) {
+ if (accelerator != Accelerator(VK_ESCAPE, false, false, false))
+ return false;
+
+ HWND hwnd = GetNativeControlHWND();
+ if (!hwnd)
+ return false;
+
+ return ::SendMessage(hwnd, CB_GETDROPPEDSTATE, 0, 0) != 0;
+}
+
+HWND ComboBox::CreateNativeControl(HWND parent_container) {
+ HWND r = ::CreateWindowEx(GetAdditionalExStyle(), L"COMBOBOX", L"",
+ WS_CHILD | WS_VSCROLL | CBS_DROPDOWNLIST,
+ 0, 0, width(), height(),
+ 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_;
+}
+
+} // namespace views
diff --git a/views/controls/combo_box.h b/views/controls/combo_box.h
new file mode 100644
index 0000000..3e8eeae
--- /dev/null
+++ b/views/controls/combo_box.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_COMBO_BOX_H_
+#define VIEWS_CONTROLS_COMBO_BOX_H_
+
+#include "views/controls/native_control.h"
+
+namespace views {
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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);
+
+ // Overridden from View.
+ virtual gfx::Size GetPreferredSize();
+ virtual bool OverrideAccelerator(const Accelerator& accelerator);
+
+ // Overridden 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);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_COMBO_BOX_H_
diff --git a/views/controls/hwnd_view.cc b/views/controls/hwnd_view.cc
new file mode 100644
index 0000000..677e60a
--- /dev/null
+++ b/views/controls/hwnd_view.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/hwnd_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/logging.h"
+#include "views/focus/focus_manager.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+static const char kViewClassName[] = "views/HWNDView";
+
+HWNDView::HWNDView() {
+}
+
+HWNDView::~HWNDView() {
+}
+
+void HWNDView::Attach(HWND hwnd) {
+ DCHECK(native_view() == NULL);
+ DCHECK(hwnd) << "Impossible detatched tab case; See crbug.com/6316";
+
+ set_native_view(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, GetWidget()->GetNativeView());
+ Layout();
+
+ // Register with the focus manager so the associated view is focused when the
+ // native control gets the focus.
+ FocusManager::InstallFocusSubclass(
+ hwnd, associated_focus_view() ? associated_focus_view() : this);
+}
+
+void HWNDView::Detach() {
+ DCHECK(native_view());
+ FocusManager::UninstallFocusSubclass(native_view());
+ set_native_view(NULL);
+ set_installed_clip(false);
+}
+
+void HWNDView::Paint(ChromeCanvas* canvas) {
+ // The area behind our window is black, so 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())
+ canvas->FillRectInt(SkColorSetRGB(255, 255, 255), 0, 0, width(), height());
+}
+
+std::string HWNDView::GetClassName() const {
+ return kViewClassName;
+}
+
+void HWNDView::ViewHierarchyChanged(bool is_add, View *parent, View *child) {
+ if (!native_view())
+ return;
+
+ Widget* widget = GetWidget();
+ if (is_add && widget) {
+ HWND parent_hwnd = ::GetParent(native_view());
+ HWND widget_hwnd = widget->GetNativeView();
+ if (parent_hwnd != widget_hwnd)
+ ::SetParent(native_view(), widget_hwnd);
+ if (IsVisibleInRootView())
+ ::ShowWindow(native_view(), SW_SHOW);
+ else
+ ::ShowWindow(native_view(), SW_HIDE);
+ Layout();
+ } else if (!is_add) {
+ ::ShowWindow(native_view(), SW_HIDE);
+ ::SetParent(native_view(), NULL);
+ }
+}
+
+void HWNDView::Focus() {
+ ::SetFocus(native_view());
+}
+
+void HWNDView::InstallClip(int x, int y, int w, int h) {
+ HRGN clip_region = CreateRectRgn(x, y, x + w, y + h);
+ // NOTE: SetWindowRgn owns the region (as well as the deleting the
+ // current region), as such we don't delete the old region.
+ SetWindowRgn(native_view(), clip_region, FALSE);
+}
+
+void HWNDView::UninstallClip() {
+ SetWindowRgn(native_view(), 0, FALSE);
+}
+
+void HWNDView::ShowWidget(int x, int y, int w, int h) {
+ UINT 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(native_view()))
+ 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(native_view(), &rect);
+ ::SetWindowPos(native_view(), 0, x, y, rect.Width(), rect.Height(),
+ swp_flags);
+
+ HRGN clip_region = CreateRectRgn(0, 0, w, h);
+ SetWindowRgn(native_view(), clip_region, FALSE);
+ set_installed_clip(true);
+ } else {
+ ::SetWindowPos(native_view(), 0, x, y, w, h, swp_flags);
+ }
+}
+
+void HWNDView::HideWidget() {
+ if (!::IsWindowVisible(native_view()))
+ return; // Currently not visible, nothing to do.
+
+ // The window is currently visible, but its clipped by another view. Hide
+ // it.
+ ::SetWindowPos(native_view(), 0, 0, 0, 0, 0,
+ SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
+ SWP_NOREDRAW | SWP_NOOWNERZORDER);
+}
+
+} // namespace views
diff --git a/views/controls/hwnd_view.h b/views/controls/hwnd_view.h
new file mode 100644
index 0000000..a8d52b3
--- /dev/null
+++ b/views/controls/hwnd_view.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_HWND_VIEW_H_
+#define VIEWS_CONTROLS_HWND_VIEW_H_
+
+#include <string>
+
+#include "views/controls/native_view_host.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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.
+//
+/////////////////////////////////////////////////////////////////////////////
+// TODO: Rename this to NativeViewHostWin.
+class HWNDView : public NativeViewHost {
+ public:
+ HWNDView();
+ virtual ~HWNDView();
+
+ // 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.
+ //
+ // This object should be added to the view hierarchy before calling this
+ // function, which will expect the parent to be valid.
+ void Attach(HWND hwnd);
+
+ // Detach the attached window handle. It will no longer be updated
+ void Detach();
+
+ // TODO(sky): convert this to native_view().
+ HWND GetHWND() const { return native_view(); }
+
+ virtual void Paint(ChromeCanvas* canvas);
+
+ // Overridden from View.
+ virtual std::string GetClassName() const;
+
+ protected:
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child);
+
+ virtual void Focus();
+
+ // NativeHostView overrides.
+ virtual void InstallClip(int x, int y, int w, int h);
+ virtual void UninstallClip();
+ virtual void ShowWidget(int x, int y, int w, int h);
+ virtual void HideWidget();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HWNDView);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_HWND_VIEW_H_
diff --git a/views/controls/image_view.cc b/views/controls/image_view.cc
new file mode 100644
index 0000000..8f58744
--- /dev/null
+++ b/views/controls/image_view.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/image_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/logging.h"
+
+namespace views {
+
+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 gfx::Size& image_size) {
+ image_size_set_ = true;
+ image_size_ = image_size;
+}
+
+bool ImageView::GetImageSize(gfx::Size* image_size) {
+ DCHECK(image_size);
+ if (image_size_set_)
+ *image_size = image_size_;
+ return image_size_set_;
+}
+
+void ImageView::ResetImageSize() {
+ image_size_set_ = false;
+}
+
+gfx::Size ImageView::GetPreferredSize() {
+ if (image_size_set_) {
+ gfx::Size image_size;
+ GetImageSize(&image_size);
+ return image_size;
+ }
+ return gfx::Size(image_.width(), 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 = width() - image_width;
+ break;
+ case CENTER:
+ *x = (width() - image_width) / 2;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ switch (vert_alignment_) {
+ case LEADING:
+ *y = 0;
+ break;
+ case TRAILING:
+ *y = height() - image_height;
+ break;
+ case CENTER:
+ *y = (height() - 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_.width() != image_width ||
+ image_size_.width() != image_height)) {
+ // Resize case
+ image_.buildMipMap(false);
+ ComputeImageOrigin(image_size_.width(), image_size_.height(), &x, &y);
+ canvas->DrawBitmapInt(image_, 0, 0, image_width, image_height,
+ x, y, image_size_.width(), image_size_.height(),
+ 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;
+ }
+}
+
+} // namespace views
diff --git a/views/controls/image_view.h b/views/controls/image_view.h
new file mode 100644
index 0000000..b7ac792
--- /dev/null
+++ b/views/controls/image_view.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_IMAGE_VIEW_H_
+#define VIEWS_CONTROLS_IMAGE_VIEW_H_
+
+#include "skia/include/SkBitmap.h"
+#include "views/view.h"
+
+class ChromeCanvas;
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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 gfx::Size& 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(gfx::Size* 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 gfx::Size GetPreferredSize();
+ 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.
+ gfx::Size 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);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_IMAGE_VIEW_H_
diff --git a/views/controls/label.cc b/views/controls/label.cc
new file mode 100644
index 0000000..1dc0b54
--- /dev/null
+++ b/views/controls/label.cc
@@ -0,0 +1,444 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/label.h"
+
+#include <math.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/gfx/insets.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/common/gfx/text_elider.h"
+#include "views/background.h"
+
+namespace views {
+
+const char Label::kViewClassName[] = "views/Label";
+
+static const SkColor kEnabledColor = SK_ColorBLACK;
+static const SkColor kDisabledColor = SkColorSetRGB(161, 161, 146);
+static const int kFocusBorderPadding = 1;
+
+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;
+ allow_character_break_ = false;
+ collapse_when_hidden_ = false;
+ rtl_alignment_mode_ = USE_UI_ALIGNMENT;
+ paint_as_focused_ = false;
+ has_focus_border_ = false;
+}
+
+Label::~Label() {
+}
+
+gfx::Size Label::GetPreferredSize() {
+ gfx::Size prefsize;
+
+ // Return a size of (0, 0) if the label is not visible and if the
+ // collapse_when_hidden_ flag is set.
+ // TODO(munjal): This logic probably belongs to the View class. But for now,
+ // put it here since putting it in View class means all inheriting classes
+ // need ot respect the collapse_when_hidden_ flag.
+ if (!IsVisible() && collapse_when_hidden_)
+ return prefsize;
+
+ if (is_multi_line_) {
+ int w = width(), h = 0;
+ ChromeCanvas::SizeStringInt(text_, font_, &w, &h, ComputeMultiLineFlags());
+ prefsize.SetSize(w, h);
+ } else {
+ prefsize = GetTextSize();
+ }
+
+ gfx::Insets insets = GetInsets();
+ prefsize.Enlarge(insets.width(), insets.height());
+ return prefsize;
+}
+
+int Label::ComputeMultiLineFlags() {
+ int flags = ChromeCanvas::MULTI_LINE;
+ if (allow_character_break_)
+ flags |= ChromeCanvas::CHARACTER_BREAK;
+ 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::CalculateDrawStringParams(
+ std::wstring* paint_text, gfx::Rect* text_bounds, int* flags) {
+ DCHECK(paint_text && text_bounds && flags);
+
+ 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_, width(), 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_) {
+ gfx::Insets insets = GetInsets();
+ text_bounds->SetRect(insets.left(),
+ insets.top(),
+ width() - insets.width(),
+ height() - insets.height());
+ *flags = ComputeMultiLineFlags();
+ } else {
+ *text_bounds = GetTextBounds();
+ *flags = 0;
+ }
+}
+
+void Label::Paint(ChromeCanvas* canvas) {
+ PaintBackground(canvas);
+ std::wstring paint_text;
+ gfx::Rect text_bounds;
+ int flags = 0;
+ CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ canvas->DrawStringInt(paint_text,
+ font_,
+ color_,
+ text_bounds.x(),
+ text_bounds.y(),
+ text_bounds.width(),
+ text_bounds.height(),
+ flags);
+
+ // The focus border always hugs the text, regardless of the label's bounds.
+ if (HasFocus() || paint_as_focused_) {
+ int w = text_bounds.width();
+ int h = 0;
+ // We explicitly OR in MULTI_LINE here since SizeStringInt seems to return
+ // an incorrect height for single line text when the MULTI_LINE flag isn't
+ // specified. o_O...
+ ChromeCanvas::SizeStringInt(paint_text, font_, &w, &h,
+ flags | ChromeCanvas::MULTI_LINE);
+ gfx::Rect focus_rect = text_bounds;
+ focus_rect.set_width(w);
+ focus_rect.set_height(h);
+ focus_rect.Inset(-kFocusBorderPadding, -kFocusBorderPadding);
+ canvas->DrawFocusRect(MirroredLeftPointForRect(focus_rect), focus_rect.y(),
+ focus_rect.width(), focus_rect.height());
+ }
+}
+
+void Label::PaintBackground(ChromeCanvas* canvas) {
+ const Background* bg = contains_mouse_ ? GetMouseOverBackground() : NULL;
+ if (!bg)
+ bg = background();
+ 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(WideToUTF8(text_));
+}
+
+gfx::Size Label::GetTextSize() {
+ if (!text_size_valid_) {
+ text_size_.SetSize(font_.GetStringWidth(text_), font_.height());
+ text_size_valid_ = true;
+ }
+
+ if (text_size_valid_)
+ return text_size_;
+ return gfx::Size();
+}
+
+int Label::GetHeightForWidth(int w) {
+ if (is_multi_line_) {
+ gfx::Insets insets = GetInsets();
+ w = std::max<int>(0, w - insets.width());
+ int h = 0;
+ ChromeCanvas cc(0, 0, true);
+ cc.SizeStringInt(text_, font_, &w, &h, ComputeMultiLineFlags());
+ return h + insets.height();
+ }
+
+ 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 the View's UI layout is right-to-left and rtl_alignment_mode_ is
+ // USE_UI_ALIGNMENT, we need to flip the alignment so that the alignment
+ // settings take into account the text directionality.
+ if (UILayoutIsRightToLeft() && rtl_alignment_mode_ == USE_UI_ALIGNMENT) {
+ if (a == ALIGN_LEFT)
+ a = ALIGN_RIGHT;
+ else if (a == ALIGN_RIGHT)
+ a = ALIGN_LEFT;
+ }
+ if (horiz_alignment_ != a) {
+ horiz_alignment_ = a;
+ SchedulePaint();
+ }
+}
+
+Label::Alignment Label::GetHorizontalAlignment() const {
+ return horiz_alignment_;
+}
+
+void Label::SetRTLAlignmentMode(RTLAlignmentMode mode) {
+ rtl_alignment_mode_ = mode;
+}
+
+Label::RTLAlignmentMode Label::GetRTLAlignmentMode() const {
+ return rtl_alignment_mode_;
+}
+
+void Label::SetMultiLine(bool f) {
+ if (f != is_multi_line_) {
+ is_multi_line_ = f;
+ SchedulePaint();
+ }
+}
+
+void Label::SetAllowCharacterBreak(bool f) {
+ if (f != allow_character_break_) {
+ allow_character_break_ = 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_) > width()) {
+ *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);
+}
+
+gfx::Insets Label::GetInsets() const {
+ gfx::Insets insets = View::GetInsets();
+ if (IsFocusable() || has_focus_border_) {
+ insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding,
+ kFocusBorderPadding, kFocusBorderPadding);
+ }
+ return insets;
+}
+
+// static
+ChromeFont Label::GetDefaultFont() {
+ return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
+}
+
+void Label::UpdateContainsMouse(const MouseEvent& event) {
+ SetContainsMouse(GetTextBounds().Contains(event.x(), event.y()));
+}
+
+void Label::SetContainsMouse(bool contains_mouse) {
+ if (contains_mouse_ == contains_mouse)
+ return;
+ contains_mouse_ = contains_mouse;
+ if (GetMouseOverBackground())
+ SchedulePaint();
+}
+
+gfx::Rect Label::GetTextBounds() {
+ gfx::Size text_size = GetTextSize();
+ gfx::Insets insets = GetInsets();
+ int avail_width = width() - insets.width();
+ // Respect the size set by the owner view
+ text_size.set_width(std::min(avail_width, text_size.width()));
+
+ int text_y = insets.top() +
+ (height() - text_size.height() - insets.height()) / 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.width()) / 2;
+ break;
+ case ALIGN_RIGHT:
+ text_x = width() - insets.right() - text_size.width();
+ break;
+ default:
+ NOTREACHED();
+ text_x = 0;
+ break;
+ }
+ return gfx::Rect(text_x, text_y, text_size.width(), text_size.height());
+}
+
+void Label::SizeToFit(int max_width) {
+ DCHECK(is_multi_line_);
+
+ std::vector<std::wstring> lines;
+ SplitString(text_, L'\n', &lines);
+
+ int label_width = 0;
+ for (std::vector<std::wstring>::const_iterator iter = lines.begin();
+ iter != lines.end(); ++iter) {
+ label_width = std::max(label_width, font_.GetStringWidth(*iter));
+ }
+
+ gfx::Insets insets = GetInsets();
+ label_width += insets.width();
+
+ if (max_width > 0)
+ label_width = std::min(label_width, max_width);
+
+ SetBounds(x(), y(), label_width, 0);
+ SizeToPreferredSize();
+}
+
+bool Label::GetAccessibleRole(AccessibilityTypes::Role* role) {
+ DCHECK(role);
+
+ *role = AccessibilityTypes::ROLE_TEXT;
+ return true;
+}
+
+bool Label::GetAccessibleName(std::wstring* name) {
+ *name = GetText();
+ return true;
+}
+
+bool Label::GetAccessibleState(AccessibilityTypes::State* state) {
+ DCHECK(state);
+
+ *state = AccessibilityTypes::STATE_READONLY;
+ return true;
+}
+
+} // namespace views
diff --git a/views/controls/label.h b/views/controls/label.h
new file mode 100644
index 0000000..fa620e4
--- /dev/null
+++ b/views/controls/label.h
@@ -0,0 +1,250 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_LABEL_H_
+#define VIEWS_CONTROLS_LABEL_H_
+
+#include "app/gfx/chrome_font.h"
+#include "googleurl/src/gurl.h"
+#include "skia/include/SkColor.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+#include "views/view.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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 following enum is used to indicate whether using the Chrome UI's
+ // alignment as the label's alignment, or autodetecting the label's
+ // alignment.
+ //
+ // If the label text originates from the Chrome UI, we should use the Chrome
+ // UI's alignment as the label's alignment.
+ //
+ // If the text originates from a web page, the text's alignment is determined
+ // based on the first character with strong directionality, disregarding what
+ // directionality the Chrome UI is. And its alignment will not be flipped
+ // around in RTL locales.
+ enum RTLAlignmentMode {
+ USE_UI_ALIGNMENT = 0,
+ AUTO_DETECT_ALIGNMENT
+ };
+
+ // 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 gfx::Size GetPreferredSize();
+
+ // 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().height() if the receiver is not multi-line
+ virtual int GetHeightForWidth(int w);
+
+ // Returns 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;
+
+ // Set horizontal alignment. If the locale is RTL, and the RTL alignment
+ // setting is set as USE_UI_ALIGNMENT, the alignment is flipped around.
+ //
+ // Caveat: for labels originating from a web page, the RTL alignment mode
+ // should be reset to AUTO_DETECT_ALIGNMENT before the horizontal alignment
+ // is set. Otherwise, the label's alignment specified as a parameter will be
+ // flipped in RTL locales. Please see the comments in SetRTLAlignmentMode for
+ // more information.
+ void SetHorizontalAlignment(Alignment a);
+
+ Alignment GetHorizontalAlignment() const;
+
+ // Set the RTL alignment mode. The RTL alignment mode is initialized to
+ // USE_UI_ALIGNMENT when the label is constructed. USE_UI_ALIGNMENT applies
+ // to every label that originates from the Chrome UI. However, if the label
+ // originates from a web page, its alignment should not be flipped around for
+ // RTL locales. For such labels, we need to set the RTL alignment mode to
+ // AUTO_DETECT_ALIGNMENT so that subsequent SetHorizontalAlignment() calls
+ // will not flip the label's alignment around.
+ void SetRTLAlignmentMode(RTLAlignmentMode mode);
+
+ RTLAlignmentMode GetRTLAlignmentMode() const;
+
+ // Set whether the label text can wrap on multiple lines.
+ // Default is false.
+ void SetMultiLine(bool f);
+
+ // Set whether the label text can be split on words.
+ // Default is false. This only works when is_multi_line is true.
+ void SetAllowCharacterBreak(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);
+
+ // Overridden from View:
+ virtual gfx::Insets GetInsets() const;
+
+ // 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);
+
+ // Accessibility accessors, overridden from View.
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role);
+ virtual bool GetAccessibleName(std::wstring* name);
+ virtual bool GetAccessibleState(AccessibilityTypes::State* state);
+
+ // Gets/sets the flag to determine whether the label should be collapsed when
+ // it's hidden (not visible). If this flag is true, the label will return a
+ // preferred size of (0, 0) when it's not visible.
+ void set_collapse_when_hidden(bool value) { collapse_when_hidden_ = value; }
+ bool collapse_when_hidden() const { return collapse_when_hidden_; }
+
+ void set_paint_as_focused(bool paint_as_focused) {
+ paint_as_focused_ = paint_as_focused;
+ }
+ void set_has_focus_border(bool has_focus_border) {
+ has_focus_border_ = has_focus_border;
+ }
+
+ private:
+ // These tests call CalculateDrawStringParams in order to verify the
+ // calculations done for drawing text.
+ FRIEND_TEST(LabelTest, DrawSingleLineString);
+ FRIEND_TEST(LabelTest, DrawMultiLineString);
+
+ static ChromeFont GetDefaultFont();
+
+ // Returns parameters to be used for the DrawString call.
+ void CalculateDrawStringParams(std::wstring* paint_text,
+ gfx::Rect* text_bounds,
+ int* flags);
+
+ // If the mouse is over the text, SetContainsMouse(true) is invoked, otherwise
+ // SetContainsMouse(false) is invoked.
+ void 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();
+ gfx::Size GetTextSize();
+ void Init(const std::wstring& text, const ChromeFont& font);
+ std::wstring text_;
+ GURL url_;
+ ChromeFont font_;
+ SkColor color_;
+ gfx::Size text_size_;
+ bool text_size_valid_;
+ bool is_multi_line_;
+ bool allow_character_break_;
+ 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_;
+ // Whether to collapse the label when it's not visible.
+ bool collapse_when_hidden_;
+ // The following member variable is used to control whether the alignment
+ // needs to be flipped around for RTL locales. Please refer to the definition
+ // of RTLAlignmentMode for more information.
+ RTLAlignmentMode rtl_alignment_mode_;
+ // When embedded in a larger control that is focusable, setting this flag
+ // allows this view to be painted as focused even when it is itself not.
+ bool paint_as_focused_;
+ // When embedded in a larger control that is focusable, setting this flag
+ // allows this view to reserve space for a focus border that it otherwise
+ // might not have because it is not itself focusable.
+ bool has_focus_border_;
+
+ DISALLOW_COPY_AND_ASSIGN(Label);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_LABEL_H_
diff --git a/views/controls/label_unittest.cc b/views/controls/label_unittest.cc
new file mode 100644
index 0000000..1ac74b1
--- /dev/null
+++ b/views/controls/label_unittest.cc
@@ -0,0 +1,441 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/border.h"
+#include "views/controls/label.h"
+
+namespace views {
+
+// All text sizing measurements (width and height) should be greater than this.
+const int kMinTextDimension = 4;
+
+TEST(LabelTest, FontProperty) {
+ Label label;
+ std::wstring font_name(L"courier");
+ ChromeFont font = ChromeFont::CreateFont(font_name, 30);
+ label.SetFont(font);
+ ChromeFont font_used = label.GetFont();
+ EXPECT_STREQ(font_name.c_str(), font_used.FontName().c_str());
+ EXPECT_EQ(30, font_used.FontSize());
+}
+
+TEST(LabelTest, TextProperty) {
+ Label label;
+ std::wstring test_text(L"A random string.");
+ label.SetText(test_text);
+ EXPECT_STREQ(test_text.c_str(), label.GetText().c_str());
+}
+
+TEST(LabelTest, UrlProperty) {
+ Label label;
+ std::string my_url("http://www.orkut.com/some/Random/path");
+ GURL url(my_url);
+ label.SetURL(url);
+ EXPECT_STREQ(my_url.c_str(), label.GetURL().spec().c_str());
+ EXPECT_STREQ(UTF8ToWide(my_url).c_str(), label.GetText().c_str());
+}
+
+TEST(LabelTest, ColorProperty) {
+ Label label;
+ SkColor color = SkColorSetARGB(20, 40, 10, 5);
+ label.SetColor(color);
+ EXPECT_EQ(color, label.GetColor());
+}
+
+TEST(LabelTest, AlignmentProperty) {
+ Label label;
+ bool reverse_alignment =
+ l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT;
+
+ label.SetHorizontalAlignment(Label::ALIGN_RIGHT);
+ EXPECT_EQ(
+ reverse_alignment ? Label::ALIGN_LEFT : Label::ALIGN_RIGHT,
+ label.GetHorizontalAlignment());
+ label.SetHorizontalAlignment(Label::ALIGN_LEFT);
+ EXPECT_EQ(
+ reverse_alignment ? Label::ALIGN_RIGHT : Label::ALIGN_LEFT,
+ label.GetHorizontalAlignment());
+ label.SetHorizontalAlignment(Label::ALIGN_CENTER);
+ EXPECT_EQ(Label::ALIGN_CENTER, label.GetHorizontalAlignment());
+
+ // The label's alignment should not be flipped if the RTL alignment mode
+ // is AUTO_DETECT_ALIGNMENT.
+ label.SetRTLAlignmentMode(Label::AUTO_DETECT_ALIGNMENT);
+ label.SetHorizontalAlignment(Label::ALIGN_RIGHT);
+ EXPECT_EQ(Label::ALIGN_RIGHT, label.GetHorizontalAlignment());
+ label.SetHorizontalAlignment(Label::ALIGN_LEFT);
+ EXPECT_EQ(Label::ALIGN_LEFT, label.GetHorizontalAlignment());
+ label.SetHorizontalAlignment(Label::ALIGN_CENTER);
+ EXPECT_EQ(Label::ALIGN_CENTER, label.GetHorizontalAlignment());
+}
+
+TEST(LabelTest, RTLAlignmentModeProperty) {
+ Label label;
+ EXPECT_EQ(Label::USE_UI_ALIGNMENT, label.GetRTLAlignmentMode());
+
+ label.SetRTLAlignmentMode(Label::AUTO_DETECT_ALIGNMENT);
+ EXPECT_EQ(Label::AUTO_DETECT_ALIGNMENT, label.GetRTLAlignmentMode());
+
+ label.SetRTLAlignmentMode(Label::USE_UI_ALIGNMENT);
+ EXPECT_EQ(Label::USE_UI_ALIGNMENT, label.GetRTLAlignmentMode());
+}
+
+TEST(LabelTest, MultiLineProperty) {
+ Label label;
+ EXPECT_FALSE(label.IsMultiLine());
+ label.SetMultiLine(true);
+ EXPECT_TRUE(label.IsMultiLine());
+ label.SetMultiLine(false);
+ EXPECT_FALSE(label.IsMultiLine());
+}
+
+TEST(LabelTest, TooltipProperty) {
+ Label label;
+ std::wstring test_text(L"My cool string.");
+ label.SetText(test_text);
+
+ std::wstring tooltip;
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+ EXPECT_STREQ(test_text.c_str(), tooltip.c_str());
+
+ std::wstring tooltip_text(L"The tooltip!");
+ label.SetTooltipText(tooltip_text);
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+ EXPECT_STREQ(tooltip_text.c_str(), tooltip.c_str());
+
+ std::wstring empty_text;
+ label.SetTooltipText(empty_text);
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+ EXPECT_STREQ(test_text.c_str(), tooltip.c_str());
+
+ // Make the label big enough to hold the text
+ // and expect there to be no tooltip.
+ label.SetBounds(0, 0, 1000, 40);
+ EXPECT_FALSE(label.GetTooltipText(0, 0, &tooltip));
+
+ // Verify that setting the tooltip still shows it.
+ label.SetTooltipText(tooltip_text);
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+ EXPECT_STREQ(tooltip_text.c_str(), tooltip.c_str());
+ // Clear out the tooltip.
+ label.SetTooltipText(empty_text);
+
+ // Shrink the bounds and the tooltip should come back.
+ label.SetBounds(0, 0, 1, 1);
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+
+ // Make the label multiline and there is no tooltip again.
+ label.SetMultiLine(true);
+ EXPECT_FALSE(label.GetTooltipText(0, 0, &tooltip));
+
+ // Verify that setting the tooltip still shows it.
+ label.SetTooltipText(tooltip_text);
+ EXPECT_TRUE(label.GetTooltipText(0, 0, &tooltip));
+ EXPECT_STREQ(tooltip_text.c_str(), tooltip.c_str());
+ // Clear out the tooltip.
+ label.SetTooltipText(empty_text);
+}
+
+TEST(LabelTest, Accessibility) {
+ Label label;
+ std::wstring test_text(L"My special text.");
+ label.SetText(test_text);
+
+ AccessibilityTypes::Role role;
+ EXPECT_TRUE(label.GetAccessibleRole(&role));
+ EXPECT_EQ(AccessibilityTypes::ROLE_TEXT, role);
+
+ std::wstring name;
+ EXPECT_TRUE(label.GetAccessibleName(&name));
+ EXPECT_STREQ(test_text.c_str(), name.c_str());
+
+ AccessibilityTypes::State state;
+ EXPECT_TRUE(label.GetAccessibleState(&state));
+ EXPECT_EQ(AccessibilityTypes::STATE_READONLY, state);
+}
+
+TEST(LabelTest, SingleLineSizing) {
+ Label label;
+ std::wstring test_text(L"A not so random string in one line.");
+ label.SetText(test_text);
+
+ // GetPreferredSize
+ gfx::Size required_size = label.GetPreferredSize();
+ EXPECT_GT(required_size.height(), kMinTextDimension);
+ EXPECT_GT(required_size.width(), kMinTextDimension);
+
+ // Test everything with borders.
+ gfx::Insets border(10, 20, 30, 40);
+ label.set_border(Border::CreateEmptyBorder(border.top(),
+ border.left(),
+ border.bottom(),
+ border.right()));
+
+ // GetPreferredSize and borders.
+ label.SetBounds(0, 0, 0, 0);
+ gfx::Size required_size_with_border = label.GetPreferredSize();
+ EXPECT_EQ(required_size_with_border.height(),
+ required_size.height() + border.height());
+ EXPECT_EQ(required_size_with_border.width(),
+ required_size.width() + border.width());
+}
+
+TEST(LabelTest, MultiLineSizing) {
+ Label label;
+ label.SetFocusable(false);
+ std::wstring test_text(L"A random string\nwith multiple lines\nand returns!");
+ label.SetText(test_text);
+ label.SetMultiLine(true);
+
+ // GetPreferredSize
+ gfx::Size required_size = label.GetPreferredSize();
+ EXPECT_GT(required_size.height(), kMinTextDimension);
+ EXPECT_GT(required_size.width(), kMinTextDimension);
+
+ // SizeToFit with unlimited width.
+ label.SizeToFit(0);
+ int required_width = label.GetLocalBounds(true).width();
+ EXPECT_GT(required_width, kMinTextDimension);
+
+ // SizeToFit with limited width.
+ label.SizeToFit(required_width - 1);
+ int constrained_width = label.GetLocalBounds(true).width();
+ EXPECT_LT(constrained_width, required_width);
+ EXPECT_GT(constrained_width, kMinTextDimension);
+
+ // Change the width back to the desire width.
+ label.SizeToFit(required_width);
+ EXPECT_EQ(required_width, label.GetLocalBounds(true).width());
+
+ // General tests for GetHeightForWidth.
+ int required_height = label.GetHeightForWidth(required_width);
+ EXPECT_GT(required_height, kMinTextDimension);
+ int height_for_constrained_width = label.GetHeightForWidth(constrained_width);
+ EXPECT_GT(height_for_constrained_width, required_height);
+ // Using the constrained width or the required_width - 1 should give the
+ // same result for the height because the constrainted width is the tight
+ // width when given "required_width - 1" as the max width.
+ EXPECT_EQ(height_for_constrained_width,
+ label.GetHeightForWidth(required_width - 1));
+
+ // Test everything with borders.
+ gfx::Insets border(10, 20, 30, 40);
+ label.set_border(Border::CreateEmptyBorder(border.top(),
+ border.left(),
+ border.bottom(),
+ border.right()));
+
+ // SizeToFit and borders.
+ label.SizeToFit(0);
+ int required_width_with_border = label.GetLocalBounds(true).width();
+ EXPECT_EQ(required_width_with_border, required_width + border.width());
+
+ // GetHeightForWidth and borders.
+ int required_height_with_border =
+ label.GetHeightForWidth(required_width_with_border);
+ EXPECT_EQ(required_height_with_border, required_height + border.height());
+
+ // Test that the border width is subtracted before doing the height
+ // calculation. If it is, then the height will grow when width
+ // is shrunk.
+ int height1 = label.GetHeightForWidth(required_width_with_border - 1);
+ EXPECT_GT(height1, required_height_with_border);
+ EXPECT_EQ(height1, height_for_constrained_width + border.height());
+
+ // GetPreferredSize and borders.
+ label.SetBounds(0, 0, 0, 0);
+ gfx::Size required_size_with_border = label.GetPreferredSize();
+ EXPECT_EQ(required_size_with_border.height(),
+ required_size.height() + border.height());
+ EXPECT_EQ(required_size_with_border.width(),
+ required_size.width() + border.width());
+}
+
+TEST(LabelTest, DrawSingleLineString) {
+ Label label;
+ label.SetFocusable(false);
+
+ // Turn off mirroring so that we don't need to figure out if
+ // align right really means align left.
+ label.EnableUIMirroringForRTLLanguages(false);
+
+ std::wstring test_text(L"Here's a string with no returns.");
+ label.SetText(test_text);
+ gfx::Size required_size(label.GetPreferredSize());
+ gfx::Size extra(22, 8);
+ label.SetBounds(0,
+ 0,
+ required_size.width() + extra.width(),
+ required_size.height() + extra.height());
+
+ // Do some basic verifications for all three alignments.
+ std::wstring paint_text;
+ gfx::Rect text_bounds;
+ int flags;
+
+ // Centered text.
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be centered horizontally and vertically.
+ EXPECT_EQ(extra.width() / 2, text_bounds.x());
+ EXPECT_EQ(extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+
+ // Left aligned text.
+ label.SetHorizontalAlignment(Label::ALIGN_LEFT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be left aligned horizontally and centered vertically.
+ EXPECT_EQ(0, text_bounds.x());
+ EXPECT_EQ(extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+
+ // Right aligned text.
+ label.SetHorizontalAlignment(Label::ALIGN_RIGHT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be right aligned horizontally and centered vertically.
+ EXPECT_EQ(extra.width(), text_bounds.x());
+ EXPECT_EQ(extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+
+ // Test single line drawing with a border.
+ gfx::Insets border(39, 34, 8, 96);
+ label.set_border(Border::CreateEmptyBorder(border.top(),
+ border.left(),
+ border.bottom(),
+ border.right()));
+
+ gfx::Size required_size_with_border(label.GetPreferredSize());
+ EXPECT_EQ(required_size.width() + border.width(),
+ required_size_with_border.width());
+ EXPECT_EQ(required_size.height() + border.height(),
+ required_size_with_border.height());
+ label.SetBounds(0,
+ 0,
+ required_size_with_border.width() + extra.width(),
+ required_size_with_border.height() + extra.height());
+
+ // Centered text with border.
+ label.SetHorizontalAlignment(Label::ALIGN_CENTER);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be centered horizontally and vertically within the border.
+ EXPECT_EQ(border.left() + extra.width() / 2, text_bounds.x());
+ EXPECT_EQ(border.top() + extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+
+ // Left aligned text with border.
+ label.SetHorizontalAlignment(Label::ALIGN_LEFT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be left aligned horizontally and centered vertically.
+ EXPECT_EQ(border.left(), text_bounds.x());
+ EXPECT_EQ(border.top() + extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+
+ // Right aligned text.
+ label.SetHorizontalAlignment(Label::ALIGN_RIGHT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ // The text should be right aligned horizontally and centered vertically.
+ EXPECT_EQ(border.left() + extra.width(), text_bounds.x());
+ EXPECT_EQ(border.top() + extra.height() / 2 , text_bounds.y());
+ EXPECT_EQ(required_size.width(), text_bounds.width());
+ EXPECT_EQ(required_size.height(), text_bounds.height());
+ EXPECT_EQ(0, flags);
+}
+
+TEST(LabelTest, DrawMultiLineString) {
+ Label label;
+ label.SetFocusable(false);
+
+ // Turn off mirroring so that we don't need to figure out if
+ // align right really means align left.
+ label.EnableUIMirroringForRTLLanguages(false);
+
+ std::wstring test_text(L"Another string\nwith returns\n\n!");
+ label.SetText(test_text);
+ label.SetMultiLine(true);
+ label.SizeToFit(0);
+
+ // Do some basic verifications for all three alignments.
+ std::wstring paint_text;
+ gfx::Rect text_bounds;
+ int flags;
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ EXPECT_EQ(0, text_bounds.x());
+ EXPECT_EQ(0, text_bounds.y());
+ EXPECT_GT(text_bounds.width(), kMinTextDimension);
+ EXPECT_GT(text_bounds.height(), kMinTextDimension);
+ EXPECT_EQ(ChromeCanvas::MULTI_LINE | ChromeCanvas::TEXT_ALIGN_CENTER, flags);
+ gfx::Rect center_bounds(text_bounds);
+
+ label.SetHorizontalAlignment(Label::ALIGN_LEFT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ EXPECT_EQ(0, text_bounds.x());
+ EXPECT_EQ(0, text_bounds.y());
+ EXPECT_GT(text_bounds.width(), kMinTextDimension);
+ EXPECT_GT(text_bounds.height(), kMinTextDimension);
+ EXPECT_EQ(ChromeCanvas::MULTI_LINE | ChromeCanvas::TEXT_ALIGN_LEFT, flags);
+
+ label.SetHorizontalAlignment(Label::ALIGN_RIGHT);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ EXPECT_EQ(0, text_bounds.x());
+ EXPECT_EQ(0, text_bounds.y());
+ EXPECT_GT(text_bounds.width(), kMinTextDimension);
+ EXPECT_GT(text_bounds.height(), kMinTextDimension);
+ EXPECT_EQ(ChromeCanvas::MULTI_LINE | ChromeCanvas::TEXT_ALIGN_RIGHT, flags);
+
+ // Test multiline drawing with a border.
+ gfx::Insets border(19, 92, 23, 2);
+ label.set_border(Border::CreateEmptyBorder(border.top(),
+ border.left(),
+ border.bottom(),
+ border.right()));
+ label.SizeToFit(0);
+ label.SetHorizontalAlignment(Label::ALIGN_CENTER);
+ paint_text.clear();
+ text_bounds.SetRect(0, 0, 0, 0);
+ label.CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
+ EXPECT_STREQ(test_text.c_str(), paint_text.c_str());
+ EXPECT_EQ(center_bounds.x() + border.left(), text_bounds.x());
+ EXPECT_EQ(center_bounds.y() + border.top(), text_bounds.y());
+ EXPECT_EQ(center_bounds.width(), text_bounds.width());
+ EXPECT_EQ(center_bounds.height(), text_bounds.height());
+ EXPECT_EQ(ChromeCanvas::MULTI_LINE | ChromeCanvas::TEXT_ALIGN_CENTER, flags);
+}
+
+} // namespace views
diff --git a/views/controls/link.cc b/views/controls/link.cc
new file mode 100644
index 0000000..aae254503
--- /dev/null
+++ b/views/controls/link.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/link.h"
+
+#include "app/gfx/chrome_font.h"
+#include "views/event.h"
+
+namespace views {
+
+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[] = "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.location()));
+ 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.location())) {
+ // Focus the link on click.
+ RequestFocus();
+
+ if (controller_)
+ controller_->LinkActivated(this, e.GetFlags());
+ }
+}
+
+bool Link::OnKeyPressed(const KeyEvent& e) {
+ if ((e.GetCharacter() == VK_SPACE) || (e.GetCharacter() == VK_RETURN)) {
+ SetHighlighted(false);
+
+ // Focus the link on key pressed.
+ RequestFocus();
+
+ if (controller_)
+ controller_->LinkActivated(this, e.GetFlags());
+
+ return true;
+ }
+ return false;
+}
+
+bool Link::OverrideAccelerator(const Accelerator& accelerator) {
+ return (accelerator.GetKeyCode() == VK_SPACE) ||
+ (accelerator.GetKeyCode() == VK_RETURN);
+}
+
+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;
+ }
+}
+
+} // namespace views
diff --git a/views/controls/link.h b/views/controls/link.h
new file mode 100644
index 0000000..6da6aa3
--- /dev/null
+++ b/views/controls/link.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_LINK_H_
+#define VIEWS_CONTROLS_LINK_H_
+
+#include "views/controls/label.h"
+
+namespace views {
+
+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 OverrideAccelerator(const Accelerator& accelerator);
+
+ 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(Link);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_LINK_H_
diff --git a/views/controls/menu/chrome_menu.cc b/views/controls/menu/chrome_menu.cc
new file mode 100644
index 0000000..a887fd5
--- /dev/null
+++ b/views/controls/menu/chrome_menu.cc
@@ -0,0 +1,2816 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/menu/chrome_menu.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "app/os_exchange_data.h"
+#include "base/base_drag_source.h"
+#include "base/gfx/native_theme.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "base/timer.h"
+#include "base/win_util.h"
+// TODO(beng): (Cleanup) remove this browser dep.
+#include "chrome/browser/drag_utils.h"
+#include "chrome/common/gfx/color_utils.h"
+#include "grit/generated_resources.h"
+#include "skia/ext/skia_utils_win.h"
+#include "views/border.h"
+#include "views/view_constants.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+
+#undef min
+#undef max
+
+using base::Time;
+using base::TimeDelta;
+
+// 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 used if the menu doesn't have icons.
+static const int kItemNoIconTopMargin = 1;
+static const int kItemNoIconBottomMargin = 3;
+
+// 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 int 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;
+
+// Are mnemonics shown? This is updated before the menus are shown.
+static bool show_mnemonics;
+
+using gfx::NativeTheme;
+
+namespace views {
+
+namespace {
+
+// Returns the font menus are to use.
+ChromeFont GetMenuFont() {
+ NONCLIENTMETRICS metrics;
+ win_util::GetNonClientMetrics(&metrics);
+
+ l10n_util::AdjustUIFont(&(metrics.lfMenuFont));
+ HFONT font = CreateFontIndirect(&metrics.lfMenuFont);
+ DLOG_ASSERT(font);
+ return ChromeFont::CreateFont(font);
+}
+
+// Calculates all sizes that we can from the OS.
+//
+// This is invoked prior to Running a menu.
+void UpdateMenuPartSizes(bool has_icons) {
+ 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;
+
+ if (has_icons) {
+ label_start = kItemLeftMargin + check_width + kIconToLabelPadding;
+ } else {
+ // If there are no icons don't pad by the icon to label padding. This
+ // makes us look close to system menus.
+ label_start = kItemLeftMargin + check_width;
+ }
+ if (render_gutter)
+ label_start += gutter_width + kGutterToLabel;
+
+ ReleaseDC(NULL, dc);
+
+ MenuItemView menu_item(NULL);
+ menu_item.SetTitle(L"blah"); // Text doesn't matter here.
+ pref_menu_height = menu_item.GetPreferredSize().height();
+}
+
+// Convenience for scrolling the view such that the origin is visible.
+static void ScrollToVisible(View* view) {
+ view->ScrollRectToVisible(0, 0, view->width(), view->height());
+}
+
+// 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 a RepeatingTimer. When Run is invoked MenuScrollTask scrolls
+// appropriately.
+
+class MenuScrollTask {
+ public:
+ MenuScrollTask() : submenu_(NULL) {
+ pixels_per_second_ = pref_menu_height * 20;
+ }
+
+ 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_.IsRunning()) {
+ scrolling_timer_.Start(TimeDelta::FromMilliseconds(kScrollTimerMS), this,
+ &MenuScrollTask::Run);
+ }
+ }
+
+ void StopScrolling() {
+ if (scrolling_timer_.IsRunning()) {
+ scrolling_timer_.Stop();
+ submenu_ = NULL;
+ }
+ }
+
+ // The menu being scrolled. Returns null if not scrolling.
+ SubmenuView* submenu() const { return submenu_; }
+
+ private:
+ 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_->height() - vis_rect.height(),
+ target_y + delta_y);
+ submenu_->ScrollRectToVisible(vis_rect.x(), target_y, vis_rect.width(),
+ vis_rect.height());
+ }
+
+ // SubmenuView being scrolled.
+ SubmenuView* submenu_;
+
+ // Direction scrolling.
+ bool is_scrolling_up_;
+
+ // Timer to periodically scroll.
+ base::RepeatingTimer<MenuScrollTask> 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_COPY_AND_ASSIGN(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 gfx::Size GetPreferredSize() {
+ return gfx::Size(kScrollArrowHeight * 2 - 1, 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, width(), height() };
+ NativeTheme::instance()->PaintMenuItemBackground(
+ NativeTheme::MENU, dc, MENU_POPUPITEM, MPI_NORMAL, false,
+ &item_bounds);
+
+ // Then the arrow.
+ int x = width() / 2;
+ int y = (height() - 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_COPY_AND_ASSIGN(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->y();
+ gfx::Size pref = child->GetPreferredSize();
+ // Constrain y to make sure we don't show past the bottom of the view.
+ y = std::max(0, std::min(pref.height() - this->height(), y));
+ child->SetY(-y);
+ }
+
+ // Returns the contents, which is the SubmenuView.
+ View* GetContents() {
+ return GetChildViewAt(0);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(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_);
+
+ set_border(Border::CreateEmptyBorder(
+ kSubmenuBorderSize, kSubmenuBorderSize,
+ kSubmenuBorderSize, kSubmenuBorderSize));
+ }
+
+ virtual void Paint(ChromeCanvas* canvas) {
+ HDC dc = canvas->beginPlatformPaint();
+ CRect bounds(0, 0, width(), height());
+ 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 = View::width() - insets.width();
+ int content_height = height() - insets.height();
+ if (!scroll_up_button_->IsVisible()) {
+ scroll_view_->SetBounds(x, y, width, content_height);
+ scroll_view_->Layout();
+ return;
+ }
+
+ gfx::Size pref = scroll_up_button_->GetPreferredSize();
+ scroll_up_button_->SetBounds(x, y, width, pref.height());
+ content_height -= pref.height();
+
+ const int scroll_view_y = y + pref.height();
+
+ pref = scroll_down_button_->GetPreferredSize();
+ scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(),
+ width, pref.height());
+ content_height -= pref.height();
+
+ scroll_view_->SetBounds(x, scroll_view_y, width, content_height);
+ scroll_view_->Layout();
+ }
+
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize();
+ scroll_up_button_->SetVisible(content_pref.height() > height());
+ scroll_down_button_->SetVisible(content_pref.height() > height());
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize();
+ gfx::Insets insets = GetInsets();
+ prefsize.Enlarge(insets.width(), insets.height());
+ return prefsize;
+ }
+
+ private:
+ // The scroll buttons.
+ View* scroll_up_button_;
+ View* scroll_down_button_;
+
+ // The scroll view.
+ MenuScrollView* scroll_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(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 = height() / 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,
+ height() };
+ 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, width(), height() };
+ NativeTheme::instance()->PaintMenuSeparator(dc, MENU_POPUPSEPARATOR,
+ MPI_NORMAL, &separator_bounds);
+ canvas->endPlatformPaint();
+ }
+
+ gfx::Size GetPreferredSize() {
+ return gfx::Size(10, // Just in case we're the only item in a menu.
+ separator_height);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(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(Widget* widget,
+ SubmenuView* submenu)
+ : RootView(widget),
+ 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.x() < 0 || event.y() < 0 || event.x() >= width() ||
+ event.y() >= height()) ||
+ !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_) {
+ forward_drag_to_menu_controller_ = false;
+ if (canceled) {
+ GetMenuController()->Cancel(true);
+ } else {
+ GetMenuController()->OnMouseReleased(submenu_, event);
+ }
+ }
+ }
+
+ 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_COPY_AND_ASSIGN(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 WidgetWin {
+ public:
+ explicit 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) {
+ WidgetWin::Init(parent, bounds, true);
+ SetContentsView(contents_view);
+ // 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();
+ WidgetWin::Hide();
+ }
+
+ virtual void HideWindow() {
+ // Make sure we release capture before hiding.
+ ReleaseCapture();
+ WidgetWin::Hide();
+ }
+
+ virtual void OnCaptureChanged(HWND hwnd) {
+ WidgetWin::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_COPY_AND_ASSIGN(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;
+
+ explicit 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_COPY_AND_ASSIGN(EmptyMenuMenuItem);
+};
+
+// static
+const int EmptyMenuMenuItem::kEmptyMenuItemViewID =
+ MenuItemView::kMenuItemViewID + 1;
+
+} // namespace
+
+// SubmenuView ---------------------------------------------------------------
+
+SubmenuView::SubmenuView(MenuItemView* parent)
+ : parent_menu_item_(parent),
+ host_(NULL),
+ drop_item_(NULL),
+ drop_position_(MenuDelegate::DROP_NONE),
+ scroll_view_container_(NULL) {
+ DCHECK(parent);
+ // We'll delete ourselves, otherwise the ScrollView would delete us on close.
+ SetParentOwned(false);
+}
+
+SubmenuView::~SubmenuView() {
+ // The menu may not have been closed yet (it will be hidden, but not
+ // necessarily closed).
+ Close();
+
+ 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;
+ SetBounds(x(), y(), parent->width(), GetPreferredSize().height());
+
+ gfx::Insets insets = GetInsets();
+ int x = insets.left();
+ int y = insets.top();
+ int menu_item_width = width() - insets.width();
+ for (int i = 0; i < GetChildViewCount(); ++i) {
+ View* child = GetChildViewAt(i);
+ gfx::Size child_pref_size = child->GetPreferredSize();
+ child->SetBounds(x, y, menu_item_width, child_pref_size.height());
+ y += child_pref_size.height();
+ }
+}
+
+gfx::Size SubmenuView::GetPreferredSize() {
+ if (GetChildViewCount() == 0)
+ return gfx::Size();
+
+ int max_width = 0;
+ int height = 0;
+ for (int i = 0; i < GetChildViewCount(); ++i) {
+ View* child = GetChildViewAt(i);
+ gfx::Size child_pref_size = child->GetPreferredSize();
+ max_width = std::max(max_width, child_pref_size.width());
+ height += child_pref_size.height();
+ }
+ gfx::Insets insets = GetInsets();
+ return gfx::Size(max_width + insets.width(), height + insets.height());
+}
+
+void SubmenuView::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& 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() == height() || !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->y() == vis_bounds.y()) {
+ first_vis_index = i;
+ break;
+ } else if (menu_item->y() > 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)->y() == vis_bounds.y()) {
+ if (first_vis_index != 0) {
+ scroll_amount = GetMenuItemAt(first_vis_index - 1)->y() -
+ vis_bounds.y();
+ first_vis_index--;
+ } else {
+ break;
+ }
+ } else {
+ scroll_amount = GetMenuItemAt(first_vis_index)->y() - vis_bounds.y();
+ }
+ } else {
+ if (first_vis_index + 1 == GetMenuItemCount())
+ break;
+ scroll_amount = GetMenuItemAt(first_vis_index + 1)->y() -
+ vis_bounds.y();
+ if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y())
+ first_vis_index++;
+ }
+ ScrollRectToVisible(0, vis_bounds.y() + scroll_amount, vis_bounds.width(),
+ vis_bounds.height());
+ vis_bounds = GetVisibleBounds();
+ }
+
+ return true;
+}
+
+bool SubmenuView::IsShowing() {
+ return host_ && host_->IsVisible();
+}
+
+void SubmenuView::ShowAt(HWND parent,
+ const gfx::Rect& bounds,
+ bool do_capture) {
+ if (host_) {
+ host_->ShowWindow(SW_SHOWNA);
+ return;
+ }
+
+ 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() {
+ if (host_) {
+ host_->Close();
+ host_ = NULL;
+ }
+}
+
+void SubmenuView::Hide() {
+ if (host_)
+ host_->HideWindow();
+}
+
+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);
+ gfx::Rect item_bounds = item->bounds();
+ 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
+bool MenuItemView::allow_task_nesting_during_run_ = false;
+
+MenuItemView::MenuItemView(MenuDelegate* delegate) {
+ // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes supplies a
+ // NULL 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 has_mnemonics) {
+ PrepareForRun(has_mnemonics);
+
+ int mouse_event_flags;
+
+ MenuController* controller = MenuController::GetActiveInstance();
+ if (controller && !controller->IsBlockingRun()) {
+ // A menu is already showing, but it isn't a blocking menu. Cancel it.
+ // We can get here during drag and drop if the user right clicks on the
+ // menu quickly after the drop.
+ controller->Cancel(true);
+ controller = NULL;
+ }
+ 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);
+}
+
+gfx::Size MenuItemView::GetPreferredSize() {
+ ChromeFont& font = GetRootMenuItem()->font_;
+ return gfx::Size(
+ font.GetStringWidth(title_) + label_start + item_right_margin,
+ font.height() + GetBottomMargin() + GetTopMargin());
+}
+
+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() {
+ if (!has_mnemonics_)
+ return 0;
+
+ 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);
+ has_icons_ = false;
+
+ 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 has_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;
+
+ has_mnemonics_ = has_mnemonics;
+
+ AddEmptyMenus();
+
+ if (!MenuController::GetActiveInstance()) {
+ // Only update the menu size if there are no menus showing, otherwise
+ // things may shift around.
+ UpdateMenuPartSizes(has_icons_);
+ }
+
+ font_ = GetMenuFont();
+
+ BOOL show_cues;
+ show_mnemonics =
+ (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) &&
+ show_cues == TRUE);
+}
+
+int MenuItemView::GetDrawStringFlags() {
+ int flags = 0;
+ if (UILayoutIsRightToLeft())
+ flags |= ChromeCanvas::TEXT_ALIGN_RIGHT;
+ else
+ flags |= ChromeCanvas::TEXT_ALIGN_LEFT;
+
+ if (has_mnemonics_) {
+ if (show_mnemonics)
+ flags |= ChromeCanvas::SHOW_PREFIX;
+ else
+ flags |= ChromeCanvas::HIDE_PREFIX;
+ }
+ return flags;
+}
+
+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,
+ height() };
+ 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, width(), height() };
+ AdjustBoundsForRTLUI(&item_bounds);
+ NativeTheme::instance()->PaintMenuItemBackground(
+ NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection,
+ &item_bounds);
+ }
+
+ int icon_x = kItemLeftMargin;
+ int top_margin = GetTopMargin();
+ int bottom_margin = GetBottomMargin();
+ int icon_y = top_margin + (height() - kItemTopMargin -
+ bottom_margin - 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, height() };
+ 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 = this->width() - item_right_margin - label_start;
+ ChromeFont& font = GetRootMenuItem()->font_;
+ gfx::Rect text_bounds(label_start, top_margin, width, font.height());
+ text_bounds.set_x(MirroredLeftPointForRect(text_bounds));
+ if (for_drag) {
+ // With different themes, it's difficult to tell what the correct foreground
+ // and background colors are for the text to draw the correct halo. Instead,
+ // just draw black on white, which will look good in most cases.
+ canvas->DrawStringWithHalo(GetTitle(), font, 0x00000000, 0xFFFFFFFF,
+ text_bounds.x(), text_bounds.y(),
+ text_bounds.width(), text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+ } else {
+ 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,
+ top_margin + (height() - top_margin -
+ bottom_margin - 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 = {
+ this->width() - item_right_margin + kLabelToArrowPadding,
+ 0,
+ 0,
+ height()
+ };
+ 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();
+ for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
+ ++i) {
+ submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts();
+ }
+}
+
+int MenuItemView::GetTopMargin() {
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_ ? kItemTopMargin : kItemNoIconTopMargin;
+}
+
+int MenuItemView::GetBottomMargin() {
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_ ?
+ kItemBottomMargin : kItemNoIconBottomMargin;
+}
+
+// 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);
+ }
+
+ // 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
+
+ MessageLoopForUI* loop = MessageLoopForUI::current();
+ if (MenuItemView::allow_task_nesting_during_run_) {
+ bool did_allow_task_nesting = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+ loop->Run(this);
+ loop->SetNestableTasksAllowed(did_allow_task_nesting);
+ } else {
+ loop->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;
+ did_capture_ = 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);
+
+ if (menu_item && menu_item->GetDelegate())
+ menu_item->GetDelegate()->SelectionChanged(menu_item);
+
+ 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.x(), event.y());
+ 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;
+ }
+
+ 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.x();
+ press_y_ = event.y();
+ }
+ 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.x(), event.y());
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (possible_drag_) {
+ if (View::ExceededDragThreshold(event.x() - press_x_,
+ event.y() - 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.
+ gfx::Point press_loc(press_x_, press_y_);
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc);
+ View::ConvertPointToView(NULL, item, &press_loc);
+ gfx::Point drag_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &drag_loc);
+ View::ConvertPointToView(NULL, item, &drag_loc);
+ ChromeCanvas canvas(item->width(), item->height(), false);
+ item->Paint(&canvas, true);
+
+ scoped_refptr<OSExchangeData> data(new OSExchangeData);
+ item->GetDelegate()->WriteDragData(item, data.get());
+ drag_utils::SetDragImageOnDataObject(canvas, item->width(),
+ item->height(), 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);
+ }
+}
+
+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.x(), event.y());
+ 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);
+ gfx::Point loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc);
+
+ // If we open a context menu just return now
+ if (part.menu->GetDelegate()->ShowContextMenu(
+ part.menu, part.menu->GetCommand(), loc.x(), loc.y(), true))
+ return;
+ }
+
+ 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.x(), event.y());
+
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (part.type == MenuPart::MENU_ITEM && part.menu) {
+ SetSelection(part.menu, true, false);
+ } else if (!part.is_scroll() && 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);
+ }
+}
+
+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();
+
+ gfx::Point screen_loc(event.location());
+ 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.x(), event.y());
+ bool over_empty_menu = false;
+ if (!menu_item) {
+ // See if we're over an empty menu.
+ menu_item = GetEmptyMenuItemAt(source, event.x(), event.y());
+ if (menu_item)
+ over_empty_menu = true;
+ }
+ MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE;
+ int drop_operation = DragDropTypes::DRAG_NONE;
+ if (menu_item) {
+ gfx::Point menu_item_loc(event.location());
+ View::ConvertPointToView(source, menu_item, &menu_item_loc);
+ MenuItemView* query_menu_item;
+ if (!over_empty_menu) {
+ int menu_item_height = menu_item->height();
+ if (menu_item->HasSubmenu() &&
+ (menu_item_loc.y() > kDropBetweenPixels &&
+ menu_item_loc.y() < (menu_item_height - 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_) {
+ // We must translate/dispatch the message here, otherwise we would drop
+ // the message on the floor.
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return false;
+ }
+
+ // NOTE: we don't get WM_ACTIVATE or anything else interesting in here.
+ switch (msg.message) {
+ case WM_CONTEXTMENU: {
+ MenuItemView* item = pending_state_.item;
+ if (item && item->GetRootMenuItem() != item) {
+ gfx::Point screen_loc(0, item->height());
+ View::ConvertPointToScreen(item, &screen_loc);
+ item->GetDelegate()->ShowContextMenu(
+ item, item->GetCommand(), screen_loc.x(), screen_loc.y(), false);
+ }
+ return true;
+ }
+
+ // 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_SYSKEYUP:
+ // We may have been shown on a system key, as such don't do anything
+ // here. If another system key is pushed we'll get a WM_SYSKEYDOWN and
+ // close the menu.
+ return true;
+
+ case WM_CANCELMODE:
+ case WM_SYSKEYDOWN:
+ // 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;
+
+ case VK_APPS:
+ 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),
+ result_(NULL),
+ drop_target_(NULL),
+ owner_(NULL),
+ possible_drag_(false),
+ valid_drop_coordinates_(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(gfx::Point(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(gfx::Point(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(gfx::Point(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;
+
+ gfx::Point 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 gfx::Point& screen_loc,
+ MenuPart* part) {
+ // Is the mouse over the scroll buttons?
+ gfx::Point 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->width() ||
+ scroll_view_loc.y() < 0 ||
+ scroll_view_loc.y() >= scroll_view_container->height()) {
+ // 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)) {
+ gfx::Point 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 gfx::Point& screen_loc) {
+ gfx::Point 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()) {
+ current_path[i]->GetSubmenu()->Hide();
+ }
+ }
+
+ // 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()) {
+ state_.item->GetSubmenu()->Hide();
+ }
+
+ 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;
+ item->GetSubmenu()->Hide();
+}
+
+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() {
+ show_timer_.Start(TimeDelta::FromMilliseconds(kShowDelay), this,
+ &MenuController::CommitPendingSelection);
+}
+
+void MenuController::StopShowTimer() {
+ show_timer_.Stop();
+}
+
+void MenuController::StartCancelAllTimer() {
+ cancel_all_timer_.Start(TimeDelta::FromMilliseconds(kCloseOnExitTime),
+ this, &MenuController::CancelAll);
+}
+
+void MenuController::StopCancelAllTimer() {
+ cancel_all_timer_.Stop();
+}
+
+gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item,
+ bool prefer_leading,
+ bool* is_leading) {
+ DCHECK(item);
+
+ SubmenuView* submenu = item->GetSubmenu();
+ DCHECK(submenu);
+
+ gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize();
+
+ // Don't let the menu go to wide. This is some where between what IE and FF
+ // do.
+ pref.set_width(std::min(pref.width(), kMaxMenuWidth));
+ if (!state_.monitor_bounds.IsEmpty())
+ pref.set_width(std::min(pref.width(), 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.width();
+ if (!state_.monitor_bounds.IsEmpty() &&
+ y + pref.height() > 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.set_height(std::min(pref.height(),
+ state_.monitor_bounds.bottom() - y));
+ } else {
+ pref.set_height(std::min(pref.height(),
+ state_.initial_bounds.y() - state_.monitor_bounds.y()));
+ y = state_.initial_bounds.y() - pref.height();
+ }
+ }
+ } else {
+ // Not the first menu; position it relative to the bounds of the menu
+ // item.
+ gfx::Point item_loc;
+ 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->width() - kSubmenuHorizontalInset;
+ if (state_.monitor_bounds.width() != 0 &&
+ x + pref.width() > state_.monitor_bounds.right()) {
+ if (layout_is_rtl)
+ *is_leading = true;
+ else
+ *is_leading = false;
+ x = item_loc.x() - pref.width() + kSubmenuHorizontalInset;
+ }
+ } else {
+ x = item_loc.x() - pref.width() + 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->width() - kSubmenuHorizontalInset;
+ }
+ }
+ y = item_loc.y() - kSubmenuBorderSize;
+ if (state_.monitor_bounds.width() != 0) {
+ pref.set_height(std::min(pref.height(), state_.monitor_bounds.height()));
+ if (y + pref.height() > state_.monitor_bounds.bottom())
+ y = state_.monitor_bounds.bottom() - pref.height();
+ if (y < state_.monitor_bounds.y())
+ y = state_.monitor_bounds.y();
+ }
+ }
+
+ if (state_.monitor_bounds.width() != 0) {
+ if (x + pref.width() > state_.monitor_bounds.right())
+ x = state_.monitor_bounds.right() - pref.width();
+ if (x < state_.monitor_bounds.x())
+ x = state_.monitor_bounds.x();
+ }
+ return gfx::Rect(x, y, pref.width(), pref.height());
+}
+
+// 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()->GetWidget()->GetNativeView() == 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) {
+ gfx::Point screen_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+ HWND window = WindowFromPoint(screen_loc.ToPOINT());
+ 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()->GetNativeView() &&
+ GetWindowThreadProcessId(submenu->host()->GetNativeView(), 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, event_type, nc_hit_result,
+ MAKELPARAM(screen_loc.x(), screen_loc.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 views
diff --git a/views/controls/menu/chrome_menu.h b/views/controls/menu/chrome_menu.h
new file mode 100644
index 0000000..02caaed
--- /dev/null
+++ b/views/controls/menu/chrome_menu.h
@@ -0,0 +1,948 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_MENU_CHROME_MENU_H_
+#define VIEWS_CONTROLS_MENU_CHROME_MENU_H_
+
+#include <list>
+#include <vector>
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_font.h"
+#include "base/gfx/point.h"
+#include "base/gfx/rect.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "skia/include/SkBitmap.h"
+#include "views/controls/menu/controller.h"
+#include "views/view.h"
+
+namespace views {
+
+class WidgetWin;
+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.
+ // Returns true if a context menu was displayed, otherwise false
+ virtual bool ShowContextMenu(MenuItemView* source,
+ int id,
+ int x,
+ int y,
+ bool is_mouse_gesture) {
+ return false;
+ }
+
+ // 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 (views::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 left or right mouse buttons.
+ virtual bool IsTriggerableEvent(const MouseEvent& e) {
+ return e.IsLeftMouseButton() || e.IsRightMouseButton();
+ }
+
+ // 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) {
+ }
+
+ // Notification that the user has highlighted the specified item.
+ virtual void SelectionChanged(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;
+
+ // If true SetNestableTasksAllowed(true) is invoked before MessageLoop::Run
+ // is invoked. This is only useful for testing and defaults to false.
+ static bool allow_task_nesting_during_run_;
+
+ // 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. |has_mnemonics| indicates
+ // whether the items have mnemonics. Mnemonics are identified by way of the
+ // character following the '&'.
+ void RunMenuAt(HWND parent,
+ const gfx::Rect& bounds,
+ AnchorPosition anchor,
+ bool has_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 gfx::Size GetPreferredSize();
+
+ // 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();
+
+ // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView
+ // doesn't have a mnemonic.
+ wchar_t GetMnemonic();
+
+ // Do we have icons? This only has effect on the top menu. Turning this on
+ // makes the menus slightly wider and taller.
+ void set_has_icons(bool has_icons) {
+ has_icons_ = has_icons;
+ }
+
+ 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 has_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();
+
+ // Returns the various margins.
+ int GetTopMargin();
+ int GetBottomMargin();
+
+ // 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_;
+
+ // Does the title have a mnemonic?
+ bool has_mnemonics_;
+
+ bool has_icons_;
+
+ 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 (a WidgetWin) 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 gfx::Size GetPreferredSize();
+
+ // View method. Overriden to schedule a paint. We do this so that when
+ // scrolling occurs, everything is repainted correctly.
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& 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();
+
+ // 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, destroying the host.
+ void Close();
+
+ // Hides the hosting window.
+ //
+ // The hosting window is hidden first, then deleted (Close) when the menu is
+ // done running. This is done to avoid deletion ordering dependencies. In
+ // particular, during drag and drop (and when a modal dialog is shown as
+ // a result of choosing a context menu) it is possible that an event is
+ // being processed by the host, so that host is on the stack when we need to
+ // close the window. If we closed the window immediately (and deleted it),
+ // when control returned back to host we would crash as host was deleted.
+ void Hide();
+
+ // 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_;
+
+ // WidgetWin 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 MessageLoopForUI::Dispatcher {
+ public:
+ friend class MenuHostRootView;
+ friend class MenuItemView;
+ 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 immediately hides all menus.
+ void Cancel(bool all);
+
+ // An alternative to Cancel(true) that can be used with a OneShotTimer.
+ void CancelAll() { return Cancel(true); }
+
+ // 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:
+ // 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 gfx::Point& 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 gfx::Point& 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_;
+
+ // As the mouse moves around submenus are not opened immediately. Instead
+ // they open after this timer fires.
+ base::OneShotTimer<MenuController> show_timer_;
+
+ // Used to invoke CancelAll(). 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.
+ base::OneShotTimer<MenuController> 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'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 views
+
+#endif // VIEWS_CONTROLS_MENU_CHROME_MENU_H_
diff --git a/views/controls/menu/controller.h b/views/controls/menu/controller.h
new file mode 100644
index 0000000..6a693cc
--- /dev/null
+++ b/views/controls/menu/controller.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_MENU_CONTROLLER_H_
+#define VIEWS_CONTROLS_MENU_CONTROLLER_H_
+
+#include <string>
+
+// TODO(beng): remove this interface and fold it into MenuDelegate.
+
+class Controller {
+ public:
+ virtual ~Controller() { }
+
+ // 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 // VIEWS_CONTROLS_MENU_CONTROLLER_H_
diff --git a/views/controls/menu/menu.cc b/views/controls/menu/menu.cc
new file mode 100644
index 0000000..1597da5
--- /dev/null
+++ b/views/controls/menu/menu.cc
@@ -0,0 +1,626 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/menu/menu.h"
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlwin.h>
+#include <atlcrack.h>
+#include <atlframe.h>
+#include <atlmisc.h>
+#include <string>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "base/gfx/rect.h"
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "views/accelerator.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;
+
+// The data of menu items needed to display.
+struct Menu::ItemData {
+ std::wstring label;
+ SkBitmap icon;
+ bool submenu;
+};
+
+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;
+ // If the label contains an accelerator, make room for tab.
+ if (data->label.find(L'\t') != std::wstring::npos)
+ lpmis->itemWidth += font.GetStringWidth(L" ");
+ 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);
+
+ // 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;
+ // Should we add kIconWidth only when icon.width() != 0 ?
+ rect.left += kItemLeftMargin + kIconWidth;
+ rect.right -= kItemRightMargin;
+ UINT format = DT_TOP | 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;
+ ChromeFont font;
+ HGDIOBJ old_font = static_cast<HFONT>(SelectObject(hDC, font.hfont()));
+ int fontsize = font.FontSize();
+
+ // If an accelerator is specified (with a tab delimiting the rest of the
+ // label from the accelerator), we have to justify the fist part on the
+ // left and the accelerator on the right.
+ // TODO(jungshik): This will break in RTL UI. Currently, he/ar use the
+ // window system UI font and will not hit here.
+ std::wstring label = data->label;
+ std::wstring accel;
+ std::wstring::size_type tab_pos = label.find(L'\t');
+ if (tab_pos != std::wstring::npos) {
+ accel = label.substr(tab_pos);
+ label = label.substr(0, tab_pos);
+ }
+ DrawTextEx(hDC, const_cast<wchar_t*>(label.data()),
+ static_cast<int>(label.size()), &rect, format | DT_LEFT, NULL);
+ if (!accel.empty())
+ DrawTextEx(hDC, const_cast<wchar_t*>(accel.data()),
+ static_cast<int>(accel.size()), &rect,
+ format | DT_RIGHT, NULL);
+ SelectObject(hDC, old_font);
+
+ // 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
+
+bool Menu::Delegate::IsRightToLeftUILayout() const {
+ return l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT;
+}
+
+const SkBitmap& Menu::Delegate::GetEmptyIcon() const {
+ if (kEmptyIcon == NULL)
+ kEmptyIcon = new SkBitmap();
+ return *kEmptyIcon;
+}
+
+Menu::Menu(Delegate* delegate, AnchorPoint anchor, HWND owner)
+ : delegate_(delegate),
+ menu_(CreatePopupMenu()),
+ anchor_(anchor),
+ owner_(owner),
+ is_menu_visible_(false),
+ owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL)) {
+ DCHECK(delegate_);
+}
+
+Menu::Menu(Menu* parent)
+ : delegate_(parent->delegate_),
+ menu_(CreatePopupMenu()),
+ anchor_(parent->anchor_),
+ owner_(parent->owner_),
+ is_menu_visible_(false),
+ owner_draw_(parent->owner_draw_) {
+}
+
+Menu::Menu(HMENU hmenu)
+ : delegate_(NULL),
+ menu_(hmenu),
+ anchor_(TOPLEFT),
+ owner_(NULL),
+ is_menu_visible_(false),
+ owner_draw_(false) {
+ DCHECK(menu_);
+}
+
+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::AddMenuItemInternal(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon,
+ HMENU submenu,
+ MenuItemType type) {
+ DCHECK(type != SEPARATOR) << "Call AddSeparator instead!";
+
+ if (label.empty() && !delegate_) {
+ // No label and no delegate; don't add an empty menu.
+ // It appears under some circumstance we're getting an empty label
+ // (l10n_util::GetString(IDS_TASK_MANAGER) returns ""). This shouldn't
+ // happen, but I'm working over the crash here.
+ NOTREACHED();
+ return;
+ }
+
+ 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.
+ views::Accelerator accelerator(0, false, false, false);
+ if (delegate_ && 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_, index, TRUE, &mii);
+}
+
+void Menu::AppendMenuItem(int item_id,
+ const std::wstring& label,
+ MenuItemType type) {
+ AddMenuItem(-1, item_id, label, type);
+}
+
+void Menu::AddMenuItem(int index,
+ int item_id,
+ const std::wstring& label,
+ MenuItemType type) {
+ if (type == SEPARATOR)
+ AddSeparator(index);
+ else
+ AddMenuItemInternal(index, item_id, label, SkBitmap(), NULL, type);
+}
+
+Menu* Menu::AppendSubMenu(int item_id, const std::wstring& label) {
+ return AddSubMenu(-1, item_id, label);
+}
+
+Menu* Menu::AddSubMenu(int index, int item_id, const std::wstring& label) {
+ return AddSubMenuWithIcon(index, item_id, label, SkBitmap());
+}
+
+Menu* Menu::AppendSubMenuWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ return AddSubMenuWithIcon(-1, item_id, label, icon);
+}
+
+Menu* Menu::AddSubMenuWithIcon(int index,
+ 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);
+ AddMenuItemInternal(index, item_id, label, icon, submenu->menu_, NORMAL);
+ return submenu;
+}
+
+void Menu::AppendMenuItemWithLabel(int item_id, const std::wstring& label) {
+ AddMenuItemWithLabel(-1, item_id, label);
+}
+
+void Menu::AddMenuItemWithLabel(int index, int item_id,
+ const std::wstring& label) {
+ AddMenuItem(index, item_id, label, Menu::NORMAL);
+}
+
+void Menu::AppendDelegateMenuItem(int item_id) {
+ AddDelegateMenuItem(-1, item_id);
+}
+
+void Menu::AddDelegateMenuItem(int index, int item_id) {
+ AddMenuItem(index, item_id, std::wstring(), Menu::NORMAL);
+}
+
+void Menu::AppendSeparator() {
+ AddSeparator(-1);
+}
+
+void Menu::AddSeparator(int index) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(menu_, index, TRUE, &mii);
+}
+
+void Menu::AppendMenuItemWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ AddMenuItemWithIcon(-1, item_id, label, icon);
+}
+
+void Menu::AddMenuItemWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ if (!owner_draw_)
+ owner_draw_ = true;
+ AddMenuItemInternal(index, item_id, label, icon, NULL, Menu::NORMAL);
+}
+
+void Menu::EnableMenuItemByID(int item_id, bool enabled) {
+ UINT enable_flags = enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
+ EnableMenuItem(menu_, item_id, MF_BYCOMMAND | enable_flags);
+}
+
+void Menu::EnableMenuItemAt(int index, bool enabled) {
+ UINT enable_flags = enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
+ EnableMenuItem(menu_, index, MF_BYPOSITION | enable_flags);
+}
+
+void Menu::SetMenuLabel(int item_id, const std::wstring& label) {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = const_cast<wchar_t*>(label.c_str());
+ mii.cch = static_cast<UINT>(label.size());
+ SetMenuItemInfo(menu_, item_id, false, &mii);
+}
+
+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();
+
+ // NOTE: we don't use TPM_RIGHTBUTTON here as it breaks selecting by way of
+ // press, drag, release. See bugs 718 and 8560.
+ 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();
+}
+
+int Menu::ItemCount() {
+ return GetMenuItemCount(menu_);
+}
diff --git a/views/controls/menu/menu.h b/views/controls/menu/menu.h
new file mode 100644
index 0000000..0be9126
--- /dev/null
+++ b/views/controls/menu/menu.h
@@ -0,0 +1,355 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTROLS_MENU_VIEWS_MENU_H_
+#define CONTROLS_MENU_VIEWS_MENU_H_
+
+#include <windows.h>
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "views/controls/menu/controller.h"
+
+class SkBitmap;
+
+namespace {
+class MenuHostWindow;
+}
+
+namespace views {
+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:
+ virtual ~Delegate() { }
+
+ // 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, views::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;
+
+ // 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;
+
+ 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_COPY_AND_ASSIGN(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
+ };
+
+ // 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);
+ // Alternatively, a Menu object can be constructed wrapping an existing
+ // HMENU. This can be used to use the convenience methods to insert
+ // menu items and manage label string ownership. However this kind of
+ // Menu object cannot use the delegate.
+ explicit Menu(HMENU hmenu);
+ virtual ~Menu();
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // 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);
+ void AddMenuItem(int index,
+ int item_id,
+ const std::wstring& label,
+ MenuItemType type);
+
+ // Append a submenu to this menu.
+ // The returned pointer is owned by this menu.
+ Menu* AppendSubMenu(int item_id,
+ const std::wstring& label);
+ Menu* AddSubMenu(int index, 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);
+ Menu* AddSubMenuWithIcon(int index,
+ 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);
+ void AddMenuItemWithLabel(int index, int item_id, const std::wstring& label);
+
+ // This is a convenience for text label menu items where the label is
+ // provided by the delegate.
+ void AppendDelegateMenuItem(int item_id);
+ void AddDelegateMenuItem(int index, int item_id);
+
+ // Adds a separator to this menu
+ void AppendSeparator();
+ void AddSeparator(int index);
+
+ // 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);
+ void AddMenuItemWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon);
+
+ // Enables or disables the item with the specified id.
+ void EnableMenuItemByID(int item_id, bool enabled);
+ void EnableMenuItemAt(int index, bool enabled);
+
+ // Sets menu label at specified index.
+ void SetMenuLabel(int item_id, const std::wstring& label);
+
+ // 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();
+
+ // Returns the number of menu items.
+ int ItemCount();
+
+ protected:
+ // The delegate that is being used to get information about the presentation.
+ Delegate* delegate_;
+
+ private:
+ // The data of menu items needed to display.
+ struct ItemData;
+
+ explicit Menu(Menu* parent);
+
+ void AddMenuItemInternal(int index,
+ 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_COPY_AND_ASSIGN(Menu);
+};
+
+#endif // CONTROLS_MENU_VIEWS_MENU_H_
diff --git a/views/controls/menu/view_menu_delegate.h b/views/controls/menu/view_menu_delegate.h
new file mode 100644
index 0000000..bb72ed3
--- /dev/null
+++ b/views/controls/menu/view_menu_delegate.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+#define VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+
+#include "base/gfx/native_widget_types.h"
+
+namespace views {
+
+class View;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ViewMenuDelegate
+//
+// 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(View* source,
+ const CPoint& pt,
+ gfx::NativeView hwnd) = 0;
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
diff --git a/views/controls/message_box_view.cc b/views/controls/message_box_view.cc
new file mode 100644
index 0000000..d9c45c2
--- /dev/null
+++ b/views/controls/message_box_view.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/message_box_view.h"
+
+#include "app/l10n_util.h"
+#include "app/message_box_flags.h"
+#include "base/clipboard.h"
+#include "base/message_loop.h"
+#include "base/scoped_clipboard_writer.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/views/standard_layout.h"
+#include "views/controls/button/checkbox.h"
+#include "views/window/client_view.h"
+#include "grit/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 views::Label(message)),
+ prompt_field_(NULL),
+ icon_(NULL),
+ checkbox_(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 views::Label(message)),
+ prompt_field_(NULL),
+ icon_(NULL),
+ checkbox_(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() {
+ return checkbox_ ? checkbox_->checked() : false;
+}
+
+void MessageBoxView::SetIcon(const SkBitmap& icon) {
+ if (!icon_)
+ icon_ = new views::ImageView();
+ icon_->SetImage(icon);
+ icon_->SetBounds(0, 0, icon.width(), icon.height());
+ ResetLayoutManager();
+}
+
+void MessageBoxView::SetCheckBoxLabel(const std::wstring& label) {
+ if (!checkbox_)
+ checkbox_ = new views::Checkbox(label);
+ else
+ checkbox_->SetLabel(label);
+ ResetLayoutManager();
+}
+
+void MessageBoxView::SetCheckBoxSelected(bool selected) {
+ if (!checkbox_)
+ return;
+ checkbox_->SetChecked(selected);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MessageBoxView, views::View overrides:
+
+void MessageBoxView::ViewHierarchyChanged(bool is_add,
+ views::View* parent,
+ views::View* child) {
+ if (child == this && is_add) {
+ if (prompt_field_)
+ prompt_field_->SelectAll();
+ }
+}
+
+bool MessageBoxView::AcceleratorPressed(
+ const views::Accelerator& accelerator) {
+ // We only accepts Ctrl-C.
+ DCHECK(accelerator.GetKeyCode() == 'C' && accelerator.IsCtrlDown());
+
+ Clipboard* clipboard = g_browser_process->clipboard();
+ if (!clipboard)
+ return false;
+
+ ScopedClipboardWriter scw(clipboard);
+ scw.WriteText(message_label_->GetText());
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MessageBoxView, private:
+
+void MessageBoxView::Init(int dialog_flags,
+ const std::wstring& default_prompt) {
+ message_label_->SetMultiLine(true);
+ message_label_->SetAllowCharacterBreak(true);
+ if (dialog_flags & MessageBoxFlags::kAutoDetectAlignment) {
+ // Determine the alignment and directionality based on the first character
+ // with strong directionality.
+ l10n_util::TextDirection direction =
+ l10n_util::GetFirstStrongCharacterDirection(message_label_->GetText());
+ views::Label::Alignment alignment;
+ if (direction == l10n_util::RIGHT_TO_LEFT)
+ alignment = views::Label::ALIGN_RIGHT;
+ else
+ alignment = views::Label::ALIGN_LEFT;
+ // In addition, we should set the RTL alignment mode as
+ // AUTO_DETECT_ALIGNMENT so that the alignment will not be flipped around
+ // in RTL locales.
+ message_label_->SetRTLAlignmentMode(views::Label::AUTO_DETECT_ALIGNMENT);
+ message_label_->SetHorizontalAlignment(alignment);
+ } else {
+ message_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ }
+
+ if (dialog_flags & MessageBoxFlags::kFlagHasPromptField) {
+ prompt_field_ = new views::TextField;
+ prompt_field_->SetText(default_prompt);
+ }
+
+ ResetLayoutManager();
+}
+
+void MessageBoxView::ResetLayoutManager() {
+ using views::GridLayout;
+ using views::ColumnSet;
+
+ // Initialize the Grid Layout Manager used for this dialog box.
+ GridLayout* layout = CreatePanelGridLayout(this);
+ SetLayoutManager(layout);
+
+ gfx::Size icon_size;
+ if (icon_)
+ icon_size = icon_->GetPreferredSize();
+
+ // 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.width(),
+ icon_size.height());
+ 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.width() + 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 (checkbox_) {
+ column_set = layout->AddColumnSet(checkbox_column_view_set_id);
+ if (icon_) {
+ column_set->AddPaddingColumn(0,
+ icon_size.width() + 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 (checkbox_) {
+ layout->AddPaddingRow(0, kRelatedControlVerticalSpacing);
+ layout->StartRow(0, checkbox_column_view_set_id);
+ layout->AddView(checkbox_);
+ }
+
+ layout->AddPaddingRow(0, kRelatedControlVerticalSpacing);
+}
diff --git a/views/controls/message_box_view.h b/views/controls/message_box_view.h
new file mode 100644
index 0000000..6356d84
--- /dev/null
+++ b/views/controls/message_box_view.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_MESSAGE_BOX_VIEW_VIEW_H_
+#define VIEWS_CONTROLS_MESSAGE_BOX_VIEW_VIEW_H_
+
+#include <string>
+
+#include "base/task.h"
+#include "views/controls/image_view.h"
+#include "views/controls/label.h"
+#include "views/controls/text_field.h"
+#include "views/view.h"
+
+namespace views {
+class Checkbox;
+}
+
+// This class displays the contents of a message box. It is intended for use
+// within a constrained window, and has options for a message, prompt, OK
+// and Cancel buttons.
+class MessageBoxView : public views::View {
+ public:
+ 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 the text box.
+ views::TextField* text_box() { return prompt_field_; }
+
+ // 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);
+
+ // Sets the state of the check-box.
+ void SetCheckBoxSelected(bool selected);
+
+ protected:
+ // Layout and Painting functions.
+ virtual void ViewHierarchyChanged(bool is_add,
+ views::View* parent,
+ views::View* child);
+
+ // Handles Ctrl-C and writes the message in the system clipboard.
+ virtual bool AcceleratorPressed(const views::Accelerator& accelerator);
+
+ private:
+ // 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.
+ views::Label* message_label_;
+
+ // Input text field for the message box.
+ views::TextField* prompt_field_;
+
+ // Icon displayed in the upper left corner of the message box.
+ views::ImageView* icon_;
+
+ // Checkbox for the message box.
+ views::Checkbox* checkbox_;
+
+ // Maximum width of the message label.
+ int message_width_;
+
+ ScopedRunnableMethodFactory<MessageBoxView> focus_grabber_factory_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MessageBoxView);
+};
+
+#endif // VIEWS_CONTROLS_MESSAGE_BOX_VIEW_VIEW_H_
diff --git a/views/controls/native_control.cc b/views/controls/native_control.cc
new file mode 100644
index 0000000..ce37193
--- /dev/null
+++ b/views/controls/native_control.cc
@@ -0,0 +1,385 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/native_control.h"
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlframe.h>
+
+#include "app/l10n_util_win.h"
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/hwnd_view.h"
+#include "views/focus/focus_manager.h"
+#include "views/widget/widget.h"
+#include "base/gfx/native_theme.h"
+
+namespace views {
+
+// 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->GetWidget()->GetNativeView());
+ ::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) {
+ TRACK_HWND_CREATION(m_hWnd);
+
+ control_ = parent_->CreateNativeControl(m_hWnd);
+ TRACK_HWND_CREATION(control_);
+
+ FocusManager::InstallFocusSubclass(control_, parent_);
+
+ // 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();
+ TRACK_HWND_DESTRUCTION(m_hWnd);
+ }
+
+ 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->background();
+ 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 && GetWidget()) {
+ ValidateNativeControl();
+ Layout();
+ }
+}
+
+void NativeControl::Layout() {
+ if (!container_ && GetWidget())
+ ValidateNativeControl();
+
+ if (hwnd_view_) {
+ gfx::Rect lb = GetLocalBounds(false);
+
+ int x = lb.x();
+ int y = lb.y();
+ 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::OnContextMenu(const CPoint& location) {
+ if (!GetContextMenuController())
+ return;
+
+ int x = location.x;
+ int y = location.y;
+ bool is_mouse = true;
+ if (x == -1 && y == -1) {
+ gfx::Point point = GetKeyboardContextMenuLocation();
+ x = point.x();
+ y = point.y();
+ is_mouse = false;
+ }
+ ShowContextMenu(x, y, is_mouse);
+}
+
+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;
+}
+
+DWORD NativeControl::GetAdditionalRTLStyle() 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::GetExtendedTooltipStyles();
+
+ 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 && native_control->NotifyOnKeyDown()) {
+ 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);
+ TRACK_HWND_DESTRUCTION(window);
+ }
+
+ return CallWindowProc(reinterpret_cast<WNDPROC>(original_handler), window,
+ message, w_param, l_param);
+}
+
+} // namespace views
diff --git a/views/controls/native_control.h b/views/controls/native_control.h
new file mode 100644
index 0000000..0573168
--- /dev/null
+++ b/views/controls/native_control.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_NATIVE_CONTROL_H_
+#define VIEWS_CONTROLS_NATIVE_CONTROL_H_
+
+#include <windows.h>
+
+#include "views/view.h"
+
+namespace views {
+
+class HWNDView;
+class NativeControlContainer;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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();
+
+ // 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;
+
+ // TODO(xji): we use the following temporary function as we transition the
+ // various native controls to use the right set of RTL flags. This function
+ // will go away (and be replaced by GetAdditionalExStyle()) once all the
+ // controls are properly transitioned.
+ DWORD GetAdditionalRTLStyle() 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_COPY_AND_ASSIGN(NativeControl);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_NATIVE_CONTROL_H_
diff --git a/views/controls/native_control_win.cc b/views/controls/native_control_win.cc
new file mode 100644
index 0000000..0c1baf8
--- /dev/null
+++ b/views/controls/native_control_win.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "views/controls/native_control_win.h"
+
+#include "app/l10n_util_win.h"
+#include "base/logging.h"
+#include "base/win_util.h"
+
+namespace views {
+
+// static
+const wchar_t* NativeControlWin::kNativeControlWinKey =
+ L"__NATIVE_CONTROL_WIN__";
+
+static const wchar_t* kNativeControlOriginalWndProcKey =
+ L"__NATIVE_CONTROL_ORIGINAL_WNDPROC__";
+
+// static
+WNDPROC NativeControlWin::original_wndproc_ = NULL;
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeControlWin, public:
+
+NativeControlWin::NativeControlWin() : HWNDView() {
+}
+
+NativeControlWin::~NativeControlWin() {
+ HWND hwnd = GetHWND();
+ if (hwnd) {
+ // Destroy the hwnd if it still exists. Otherwise we won't have shut things
+ // down correctly, leading to leaking and crashing if another message
+ // comes in for the hwnd.
+ Detach();
+ DestroyWindow(hwnd);
+ }
+}
+
+bool NativeControlWin::ProcessMessage(UINT message, WPARAM w_param,
+ LPARAM l_param, LRESULT* result) {
+ switch (message) {
+ case WM_CONTEXTMENU:
+ ShowContextMenu(gfx::Point(LOWORD(l_param), HIWORD(l_param)));
+ *result = 0;
+ return true;
+ case WM_CTLCOLORBTN:
+ case WM_CTLCOLORSTATIC:
+ *result = GetControlColor(message, reinterpret_cast<HDC>(w_param),
+ GetHWND());
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeControlWin, View overrides:
+
+void NativeControlWin::SetEnabled(bool enabled) {
+ if (IsEnabled() != enabled) {
+ View::SetEnabled(enabled);
+ if (GetHWND())
+ EnableWindow(GetHWND(), IsEnabled());
+ }
+}
+
+void NativeControlWin::ViewHierarchyChanged(bool is_add, View* parent,
+ View* child) {
+ // Create the HWND when we're added to a valid Widget. Many controls need a
+ // parent HWND to function properly.
+ if (is_add && GetWidget() && !GetHWND())
+ CreateNativeControl();
+
+ // Call the base class to hide the view if we're being removed.
+ HWNDView::ViewHierarchyChanged(is_add, parent, child);
+}
+
+void NativeControlWin::VisibilityChanged(View* starting_from, bool is_visible) {
+ if (!is_visible) {
+ // We destroy the child control HWND when we become invisible because of the
+ // performance cost of maintaining many HWNDs.
+ HWND hwnd = GetHWND();
+ Detach();
+ DestroyWindow(hwnd);
+ } else if (!GetHWND()) {
+ CreateNativeControl();
+ }
+}
+
+void NativeControlWin::Focus() {
+ DCHECK(GetHWND());
+ SetFocus(GetHWND());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeControlWin, protected:
+
+void NativeControlWin::ShowContextMenu(const gfx::Point& location) {
+ if (!GetContextMenuController())
+ return;
+
+ int x = location.x();
+ int y = location.y();
+ bool is_mouse = true;
+ if (x == -1 && y == -1) {
+ gfx::Point point = GetKeyboardContextMenuLocation();
+ x = point.x();
+ y = point.y();
+ is_mouse = false;
+ }
+ View::ShowContextMenu(x, y, is_mouse);
+}
+
+void NativeControlWin::NativeControlCreated(HWND native_control) {
+ TRACK_HWND_CREATION(native_control);
+
+ // Associate this object with the control's HWND so that WidgetWin can find
+ // this object when it receives messages from it.
+ SetProp(native_control, kNativeControlWinKey, this);
+
+ // Subclass the window so we can monitor for key presses.
+ original_wndproc_ =
+ win_util::SetWindowProc(native_control,
+ &NativeControlWin::NativeControlWndProc);
+ SetProp(native_control, kNativeControlOriginalWndProcKey, original_wndproc_);
+
+ Attach(native_control);
+ // GetHWND() is now valid.
+
+ // Update the newly created HWND with any resident enabled state.
+ EnableWindow(GetHWND(), IsEnabled());
+
+ // This message ensures that the focus border is shown.
+ SendMessage(GetHWND(), WM_CHANGEUISTATE,
+ MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS), 0);
+}
+
+DWORD NativeControlWin::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;
+}
+
+DWORD NativeControlWin::GetAdditionalRTLStyle() 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::GetExtendedTooltipStyles();
+
+ return ex_style;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeControlWin, private:
+
+LRESULT NativeControlWin::GetControlColor(UINT message, HDC dc, HWND sender) {
+ View *ancestor = this;
+ while (ancestor) {
+ const Background* background = ancestor->background();
+ 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));
+}
+
+// static
+LRESULT NativeControlWin::NativeControlWndProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
+ NativeControlWin* native_control =
+ static_cast<NativeControlWin*>(GetProp(window, kNativeControlWinKey));
+ DCHECK(native_control);
+
+ if (message == WM_KEYDOWN && native_control->NotifyOnKeyDown()) {
+ if (native_control->OnKeyDown(static_cast<int>(w_param)))
+ return 0;
+ } else if (message == WM_DESTROY) {
+ win_util::SetWindowProc(window, native_control->original_wndproc_);
+ RemoveProp(window, kNativeControlWinKey);
+ TRACK_HWND_DESTRUCTION(window);
+ }
+
+ return CallWindowProc(native_control->original_wndproc_, window, message,
+ w_param, l_param);
+}
+
+} // namespace views
diff --git a/views/controls/native_control_win.h b/views/controls/native_control_win.h
new file mode 100644
index 0000000..6f9923f
--- /dev/null
+++ b/views/controls/native_control_win.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_CONTROLS_NATIVE_CONTROL_WIN_H_
+#define VIEWS_CONTROLS_NATIVE_CONTROL_WIN_H_
+
+#include "views/controls/hwnd_view.h"
+
+namespace views {
+
+// A View that hosts a native Windows control.
+class NativeControlWin : public HWNDView {
+ public:
+ static const wchar_t* kNativeControlWinKey;
+
+ NativeControlWin();
+ virtual ~NativeControlWin();
+
+ // Called by the containing WidgetWin when a message is received from the HWND
+ // created by an object derived from NativeControlWin. Derived classes MUST
+ // call _this_ version of the function if they override it and do not handle
+ // all of the messages listed in widget_win.cc ProcessNativeControlWinMessage.
+ // Returns true if the message was handled, with a valid result in |result|.
+ // Returns false if the message was not handled.
+ virtual bool ProcessMessage(UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* result);
+
+ // Called by our subclassed window procedure when a WM_KEYDOWN message is
+ // received by the HWND created by an object derived from NativeControlWin.
+ // Returns true if the key was processed, false otherwise.
+ virtual bool OnKeyDown(int vkey) { return false; }
+
+ // Overridden from View:
+ virtual void SetEnabled(bool enabled);
+
+ protected:
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child);
+ virtual void VisibilityChanged(View* starting_from, bool is_visible);
+ virtual void Focus();
+
+ // Called by the containing WidgetWin when a WM_CONTEXTMENU message is
+ // received from the HWND created by an object derived from NativeControlWin.
+ virtual void ShowContextMenu(const gfx::Point& location);
+
+ // 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; }
+
+ // Called when the NativeControlWin is attached to a View hierarchy with a
+ // valid Widget. The NativeControlWin should use this opportunity to create
+ // its associated HWND.
+ virtual void CreateNativeControl() = 0;
+
+ // MUST be called by the subclass implementation of |CreateNativeControl|
+ // immediately after creating the control HWND, otherwise it won't be attached
+ // to the HWNDView and will be effectively orphaned.
+ virtual void NativeControlCreated(HWND native_control);
+
+ // 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;
+
+ // TODO(xji): we use the following temporary function as we transition the
+ // various native controls to use the right set of RTL flags. This function
+ // will go away (and be replaced by GetAdditionalExStyle()) once all the
+ // controls are properly transitioned.
+ DWORD GetAdditionalRTLStyle() const;
+
+ private:
+ // Called by the containing WidgetWin when a message of type WM_CTLCOLORBTN or
+ // WM_CTLCOLORSTATIC is sent from the HWND created by an object dreived from
+ // NativeControlWin.
+ LRESULT GetControlColor(UINT message, HDC dc, HWND sender);
+
+ // Our subclass window procedure for the attached control.
+ static LRESULT CALLBACK NativeControlWndProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param);
+
+ // The window procedure before we subclassed.
+ static WNDPROC original_wndproc_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeControlWin);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_NATIVE_CONTROL_WIN_H_
diff --git a/views/controls/native_view_host.cc b/views/controls/native_view_host.cc
new file mode 100644
index 0000000..3fc5fba
--- /dev/null
+++ b/views/controls/native_view_host.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/native_view_host.h"
+
+#include "views/widget/widget.h"
+#include "base/logging.h"
+
+namespace views {
+
+NativeViewHost::NativeViewHost()
+ : native_view_(NULL),
+ installed_clip_(false),
+ fast_resize_(false),
+ focus_view_(NULL) {
+ // The native widget is 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);
+}
+
+NativeViewHost::~NativeViewHost() {
+}
+
+gfx::Size NativeViewHost::GetPreferredSize() {
+ return preferred_size_;
+}
+
+void NativeViewHost::Layout() {
+ if (!native_view_)
+ return;
+
+ // Since widgets know nothing about the View hierarchy (they are direct
+ // children of the Widget that hosts our View hierarchy) they need to be
+ // positioned in the coordinate system of the Widget, not the current
+ // view.
+ gfx::Point top_left;
+ ConvertPointToWidget(this, &top_left);
+
+ gfx::Rect vis_bounds = GetVisibleBounds();
+ bool visible = !vis_bounds.IsEmpty();
+
+ if (visible && !fast_resize_) {
+ if (vis_bounds.size() != size()) {
+ // Only a portion of the Widget is really visible.
+ int x = vis_bounds.x();
+ int y = vis_bounds.y();
+ InstallClip(x, y, vis_bounds.width(), vis_bounds.height());
+ installed_clip_ = true;
+ } else if (installed_clip_) {
+ // The whole widget is visible but we installed a clip on the widget,
+ // uninstall it.
+ UninstallClip();
+ installed_clip_ = false;
+ }
+ }
+
+ if (visible) {
+ ShowWidget(top_left.x(), top_left.y(), width(), height());
+ } else {
+ HideWidget();
+ }
+}
+
+void NativeViewHost::VisibilityChanged(View* starting_from, bool is_visible) {
+ Layout();
+}
+
+void NativeViewHost::VisibleBoundsInRootChanged() {
+ Layout();
+}
+
+} // namespace views
diff --git a/views/controls/native_view_host.h b/views/controls/native_view_host.h
new file mode 100644
index 0000000..99b85b6
--- /dev/null
+++ b/views/controls/native_view_host.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_NATIVE_VIEW_HOST_H_
+#define VIEWS_CONTROLS_NATIVE_VIEW_HOST_H_
+
+#include <string>
+
+#include "views/view.h"
+
+#include "base/gfx/native_widget_types.h"
+
+namespace views {
+
+// Base class for embedding native widgets in a view.
+class NativeViewHost : public View {
+ public:
+ NativeViewHost();
+ virtual ~NativeViewHost();
+
+ void set_preferred_size(const gfx::Size& size) { preferred_size_ = size; }
+
+ // Returns the preferred size set via set_preferred_size.
+ virtual gfx::Size GetPreferredSize();
+
+ // Overriden to invoke Layout.
+ virtual void VisibilityChanged(View* starting_from, bool is_visible);
+
+ // Invokes any of InstallClip, UninstallClip, ShowWidget or HideWidget
+ // depending upon what portion of the widget is view in the parent.
+ virtual void Layout();
+
+ // A NativeViewHost 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
+ // NativeViewHost directly wraps a native window as is, the associated view
+ // is this View. In other cases where the NativeViewHost is part of another
+ // view (such as TextField), the actual View is not the NativeViewHost and
+ // this method must be called to set that.
+ // This method must be called before Attach().
+ void SetAssociatedFocusView(View* view) { focus_view_ = view; }
+ View* associated_focus_view() { return focus_view_; }
+
+ void set_fast_resize(bool fast_resize) { fast_resize_ = fast_resize; }
+ bool fast_resize() const { return fast_resize_; }
+
+ // The embedded native view.
+ gfx::NativeView native_view() const { return native_view_; }
+
+ protected:
+ // Notification that our visible bounds relative to the root has changed.
+ // Invokes Layout to make sure the widget is positioned correctly.
+ virtual void VisibleBoundsInRootChanged();
+
+ // Sets the native view. Subclasses will typically invoke Layout after setting
+ // the widget.
+ void set_native_view(gfx::NativeView widget) { native_view_ = widget; }
+
+ // Installs a clip on the native widget.
+ virtual void InstallClip(int x, int y, int w, int h) = 0;
+
+ // Removes the clip installed on the native widget by way of InstallClip.
+ virtual void UninstallClip() = 0;
+
+ // Shows the widget at the specified position (relative to the parent widget).
+ virtual void ShowWidget(int x, int y, int w, int h) = 0;
+
+ // Hides the widget. NOTE: this may be invoked when the widget is already
+ // hidden.
+ virtual void HideWidget() = 0;
+
+ void set_installed_clip(bool installed_clip) {
+ installed_clip_ = installed_clip;
+ }
+ bool installed_clip() const { return installed_clip_; }
+
+ private:
+ gfx::NativeView native_view_;
+
+ // The preferred size of this View
+ gfx::Size 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 NativeViewHost is focused.
+ View* focus_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeViewHost);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_NATIVE_VIEW_HOST_H_
diff --git a/views/controls/scroll_view.cc b/views/controls/scroll_view.cc
new file mode 100644
index 0000000..680b623
--- /dev/null
+++ b/views/controls/scroll_view.cc
@@ -0,0 +1,517 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/scroll_view.h"
+
+#include "app/resource_bundle.h"
+#include "base/logging.h"
+#include "grit/theme_resources.h"
+#include "views/controls/scrollbar/native_scroll_bar.h"
+#include "views/widget/root_view.h"
+
+namespace views {
+
+const char* const ScrollView::kViewClassName = "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->x();
+ y -= contents->y();
+ 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 gfx::Size& vp_size,
+ const gfx::Size& 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.width() <= vp_size.width() &&
+ content_size.height() <= vp_size.height()) {
+ *horiz_is_shown = false;
+ *vert_is_shown = false;
+ } else if (content_size.width() <= vp_size.width() - GetScrollBarWidth()) {
+ *horiz_is_shown = false;
+ *vert_is_shown = true;
+ } else if (content_size.height() <= vp_size.height() - 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.
+ gfx::Rect viewport_bounds = GetLocalBounds(true);
+ // Realign it to 0 so it can be used as-is for SetBounds().
+ viewport_bounds.set_origin(gfx::Point(0, 0));
+ // viewport_size is the total client space available.
+ gfx::Size viewport_size = viewport_bounds.size();
+ if (viewport_bounds.IsEmpty()) {
+ // 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.set_width(viewport_bounds.width() - 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_) {
+ gfx::Size content_size = contents_->size();
+ 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.set_height(viewport_bounds.height() - horiz_sb_height);
+ should_layout_contents = true;
+ }
+ // Default.
+ if (!vert_sb_required) {
+ viewport_bounds.set_width(viewport_bounds.width() + 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_->width(),
+ contents_->width(),
+ -contents_->x());
+ y = CheckScrollBounds(viewport_->height(),
+ contents_->height(),
+ -contents_->y());
+
+ // This is no op if bounds are the same
+ contents_->SetBounds(-x, -y, contents_->width(), contents_->height());
+ }
+}
+
+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_->width(), viewport_->height());
+}
+
+void ScrollView::ScrollContentsRegionToBeVisible(int x,
+ int y,
+ int width,
+ int height) {
+ if (!contents_ || ((!horiz_sb_ || !horiz_sb_->IsVisible()) &&
+ (!vert_sb_ || !vert_sb_->IsVisible()))) {
+ return;
+ }
+
+ // Figure out the maximums for this scroll view.
+ const int contents_max_x =
+ std::max(viewport_->width(), contents_->width());
+ const int contents_max_y =
+ std::max(viewport_->height(), contents_->height());
+
+ // Make sure x and y are within the bounds of [0,contents_max_*].
+ x = std::max(0, std::min(contents_max_x, x));
+ y = std::max(0, std::min(contents_max_y, y));
+
+ // Figure out how far and down the rectangle will go taking width
+ // and height into account. This will be "clipped" by the viewport.
+ const int max_x = std::min(contents_max_x,
+ x + std::min(width, viewport_->width()));
+ const int max_y = std::min(contents_max_y,
+ y + std::min(height,
+ viewport_->height()));
+
+ // See if the rect is already visible. Note the width is (max_x - x)
+ // and the height is (max_y - y) to take into account the clipping of
+ // either viewport or the content size.
+ const gfx::Rect vis_rect = GetVisibleRect();
+ if (vis_rect.Contains(gfx::Rect(x, y, max_x - x, max_y - y)))
+ return;
+
+ // Shift contents_'s X and Y so that the region is visible. If we
+ // need to shift up or left from where we currently are then we need
+ // to get it so that the content appears in the upper/left
+ // corner. This is done by setting the offset to -X or -Y. For down
+ // or right shifts we need to make sure it appears in the
+ // lower/right corner. This is calculated by taking max_x or max_y
+ // and scaling it back by the size of the viewport.
+ const int new_x =
+ (vis_rect.x() > x) ? x : std::max(0, max_x - viewport_->width());
+ const int new_y =
+ (vis_rect.y() > y) ? y : std::max(0, max_y - viewport_->height());
+
+ contents_->SetX(-new_x);
+ contents_->SetY(-new_y);
+ UpdateScrollBarPositions();
+}
+
+void ScrollView::UpdateScrollBarPositions() {
+ if (!contents_) {
+ return;
+ }
+
+ if (horiz_sb_->IsVisible()) {
+ int vw = viewport_->width();
+ int cw = contents_->width();
+ int origin = contents_->x();
+ horiz_sb_->Update(vw, cw, -origin);
+ }
+ if (vert_sb_->IsVisible()) {
+ int vh = viewport_->height();
+ int ch = contents_->height();
+ int origin = contents_->y();
+ 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_->width();
+ int cw = contents_->width();
+ int origin = contents_->x();
+ 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);
+ contents_->SchedulePaint(contents_->GetLocalBounds(true), true);
+ }
+ } else if (source == vert_sb_ && vert_sb_->IsVisible()) {
+ int vh = viewport_->height();
+ int ch = contents_->height();
+ int origin = contents_->y();
+ 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);
+ contents_->SchedulePaint(contents_->GetLocalBounds(true), 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_->width() : viewport_->height();
+ return is_horizontal ? viewport_->width() / 5 : viewport_->height() / 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()->y());
+ int vis_height = scroll_view->GetContents()->GetParent()->height();
+ if (is_positive) {
+ // Align the bottom most row to the top of the view.
+ int bottom = std::min(scroll_view->GetContents()->height() - 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()->y());
+ 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_);
+}
+
+} // namespace views
diff --git a/views/controls/scroll_view.h b/views/controls/scroll_view.h
new file mode 100644
index 0000000..5a578cd
--- /dev/null
+++ b/views/controls/scroll_view.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SCROLL_VIEW_H_
+#define VIEWS_CONTROLS_SCROLL_VIEW_H_
+
+#include "views/controls/scrollbar/scroll_bar.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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 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();
+
+ // 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 gfx::Size& viewport_size,
+ const gfx::Size& 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);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_SCROLL_VIEW_H_
diff --git a/views/controls/scrollbar/bitmap_scroll_bar.cc b/views/controls/scrollbar/bitmap_scroll_bar.cc
new file mode 100644
index 0000000..4a93782
--- /dev/null
+++ b/views/controls/scrollbar/bitmap_scroll_bar.cc
@@ -0,0 +1,703 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/scrollbar/bitmap_scroll_bar.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "base/message_loop.h"
+#include "grit/generated_resources.h"
+#include "skia/include/SkBitmap.h"
+#include "views/controls/menu/menu.h"
+#include "views/controls/scroll_view.h"
+#include "views/widget/widget.h"
+
+#undef min
+#undef max
+
+namespace views {
+
+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 ImageButton {
+ public:
+ AutorepeatButton(ButtonListener* listener)
+ : ImageButton(listener),
+ 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() {
+ Button::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_(CustomButton::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.
+ gfx::Size prefsize = GetPreferredSize();
+ size = std::max(size,
+ static_cast<int>(scroll_bar_->IsHorizontal() ?
+ prefsize.width() : prefsize.height()));
+ gfx::Rect thumb_bounds = bounds();
+ if (scroll_bar_->IsHorizontal()) {
+ thumb_bounds.set_width(size);
+ } else {
+ thumb_bounds.set_height(size);
+ }
+ SetBounds(thumb_bounds);
+ }
+
+ // Retrieves the size (width or height) of the thumb.
+ int GetSize() const {
+ if (scroll_bar_->IsHorizontal())
+ return width();
+ return height();
+ }
+
+ // Sets the position of the thumb on the x or y axis.
+ void SetPosition(int position) {
+ gfx::Rect thumb_bounds = bounds();
+ gfx::Rect track_bounds = scroll_bar_->GetTrackBounds();
+ if (scroll_bar_->IsHorizontal()) {
+ thumb_bounds.set_x(track_bounds.x() + position);
+ } else {
+ thumb_bounds.set_x(track_bounds.y() + position);
+ }
+ SetBounds(thumb_bounds);
+ }
+
+ // Gets the position of the thumb on the x or y axis.
+ int GetPosition() const {
+ gfx::Rect track_bounds = scroll_bar_->GetTrackBounds();
+ if (scroll_bar_->IsHorizontal())
+ return x() - track_bounds.x();
+ return y() - track_bounds.y();
+ }
+
+ // View overrides:
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(background_bitmap()->width(),
+ 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 = height() - 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,
+ height() - bottom_cap_height);
+
+ // Paint the grippy over the track.
+ int grippy_x = (width() - 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(CustomButton::BS_HOT);
+ }
+
+ virtual void OnMouseExited(const MouseEvent& event) {
+ SetState(CustomButton::BS_NORMAL);
+ }
+
+ virtual bool OnMousePressed(const MouseEvent& event) {
+ mouse_offset_ = scroll_bar_->IsHorizontal() ? event.x() : event.y();
+ drag_start_position_ = GetPosition();
+ SetState(CustomButton::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.y() < y() - kScrollThumbDragOutSnap) ||
+ (event.y() > (y() + height() + kScrollThumbDragOutSnap))) {
+ scroll_bar_->ScrollToThumbPosition(drag_start_position_, false);
+ return true;
+ }
+ } else {
+ if ((event.x() < x() - kScrollThumbDragOutSnap) ||
+ (event.x() > (x() + width() + kScrollThumbDragOutSnap))) {
+ scroll_bar_->ScrollToThumbPosition(drag_start_position_, false);
+ return true;
+ }
+ }
+ if (scroll_bar_->IsHorizontal()) {
+ int thumb_x = event.x() - mouse_offset_;
+ scroll_bar_->ScrollToThumbPosition(x() + thumb_x, false);
+ } else {
+ int thumb_y = event.y() - mouse_offset_;
+ scroll_bar_->ScrollToThumbPosition(y() + thumb_y, false);
+ }
+ return true;
+ }
+
+ virtual void OnMouseReleased(const MouseEvent& event,
+ bool canceled) {
+ SetState(CustomButton::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]
+ [CustomButton::BS_NORMAL];
+ }
+
+ // Update our state and schedule a repaint when the mouse moves over us.
+ void SetState(CustomButton::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.
+ CustomButton::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(this)),
+ next_button_(new AutorepeatButton(this)),
+ thumb_(new BitmapScrollBarThumb(this)),
+ thumb_track_state_(CustomButton::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);
+ }
+
+ 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 {
+ gfx::Size prefsize = prev_button_->GetPreferredSize();
+ if (IsHorizontal()) {
+ if (!show_scroll_buttons_)
+ prefsize.set_width(0);
+ int new_width =
+ std::max(0, static_cast<int>(width() - (prefsize.width() * 2)));
+ gfx::Rect track_bounds(prefsize.width(), 0, new_width, prefsize.height());
+ return track_bounds;
+ }
+ if (!show_scroll_buttons_)
+ prefsize.set_height(0);
+ gfx::Rect track_bounds(0, prefsize.height(), prefsize.width(),
+ std::max(0, height() - (prefsize.height() * 2)));
+ return track_bounds;
+}
+
+void BitmapScrollBar::SetImage(ScrollBarPart part,
+ CustomButton::ButtonState state,
+ SkBitmap* bitmap) {
+ DCHECK(part < PART_COUNT);
+ DCHECK(state < CustomButton::BS_COUNT);
+ 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)
+ ScrollByAmount(last_scroll_amount_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// BitmapScrollBar, View implementation:
+
+gfx::Size BitmapScrollBar::GetPreferredSize() {
+ // In this case, we're returning the desired width of the scrollbar and its
+ // minimum allowable height.
+ gfx::Size button_prefsize = prev_button_->GetPreferredSize();
+ return gfx::Size(button_prefsize.width(), button_prefsize.height() * 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_) {
+ gfx::Size prefsize = prev_button_->GetPreferredSize();
+ prev_button_->SetBounds(0, 0, prefsize.width(), prefsize.height());
+ prefsize = next_button_->GetPreferredSize();
+ if (IsHorizontal()) {
+ next_button_->SetBounds(width() - prefsize.width(), 0, prefsize.width(),
+ prefsize.height());
+ } else {
+ next_button_->SetBounds(0, height() - prefsize.height(), prefsize.width(),
+ prefsize.height());
+ }
+ } else {
+ prev_button_->SetBounds(0, 0, 0, 0);
+ next_button_->SetBounds(0, 0, 0, 0);
+ }
+
+ // Size and place the thumb
+ gfx::Size thumb_prefsize = thumb_->GetPreferredSize();
+ 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.
+ if (IsHorizontal()) {
+ thumb_->SetBounds(thumb_->x(), thumb_->y(), thumb_->width(),
+ thumb_prefsize.height());
+ } else {
+ thumb_->SetBounds(thumb_->x(), thumb_->y(), thumb_prefsize.width(),
+ thumb_->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.width()) ||
+ (!IsHorizontal() && (track_bounds.height() < thumb_prefsize.height())))) {
+ thumb_->SetVisible(false);
+ } else if (!thumb_->IsVisible()) {
+ thumb_->SetVisible(true);
+ }
+}
+
+bool BitmapScrollBar::OnMousePressed(const MouseEvent& event) {
+ if (event.IsOnlyLeftMouseButton()) {
+ SetThumbTrackState(CustomButton::BS_PUSHED);
+ gfx::Rect thumb_bounds = thumb_->bounds();
+ if (IsHorizontal()) {
+ if (event.x() < thumb_bounds.x()) {
+ last_scroll_amount_ = SCROLL_PREV_PAGE;
+ } else if (event.x() > thumb_bounds.right()) {
+ last_scroll_amount_ = SCROLL_NEXT_PAGE;
+ }
+ } else {
+ if (event.y() < thumb_bounds.y()) {
+ last_scroll_amount_ = SCROLL_PREV_PAGE;
+ } else if (event.y() > thumb_bounds.bottom()) {
+ last_scroll_amount_ = SCROLL_NEXT_PAGE;
+ }
+ }
+ TrackClicked();
+ repeater_.Start();
+ }
+ return true;
+}
+
+void BitmapScrollBar::OnMouseReleased(const MouseEvent& event, bool canceled) {
+ SetThumbTrackState(CustomButton::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) {
+ Widget* widget = GetWidget();
+ gfx::Rect widget_bounds;
+ widget->GetBounds(&widget_bounds, true);
+ gfx::Point temp_pt(x - widget_bounds.x(), y - widget_bounds.y());
+ View::ConvertPointFromWidget(this, &temp_pt);
+ context_menu_mouse_position_ = IsHorizontal() ? temp_pt.x() : temp_pt.y();
+
+ Menu menu(this, Menu::TOPLEFT, GetWidget()->GetNativeView());
+ 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, ButtonListener implementation:
+
+void BitmapScrollBar::ButtonPressed(Button* 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 {
+ gfx::Size prefsize = prev_button_->GetPreferredSize();
+ return IsHorizontal() ? prefsize.height() : prefsize.width();
+}
+
+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(CustomButton::ButtonState state) {
+ thumb_track_state_ = state;
+ SchedulePaint();
+}
+
+} // namespace views
diff --git a/views/controls/scrollbar/bitmap_scroll_bar.h b/views/controls/scrollbar/bitmap_scroll_bar.h
new file mode 100644
index 0000000..45e3535
--- /dev/null
+++ b/views/controls/scrollbar/bitmap_scroll_bar.h
@@ -0,0 +1,192 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SCROLLBAR_BITMAP_SCROLL_BAR_H_
+#define VIEWS_CONTROLS_SCROLLBAR_BITMAP_SCROLL_BAR_H_
+
+#include "views/controls/button/image_button.h"
+#include "views/controls/menu/menu.h"
+#include "views/controls/scrollbar/scroll_bar.h"
+#include "views/repeat_controller.h"
+
+namespace views {
+
+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 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 {
+ // The button used to represent scrolling up/left by 1 line.
+ PREV_BUTTON = 0,
+ // 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.
+ NEXT_BUTTON,
+ // The top/left segment of the thumb on the scrollbar.
+ THUMB_START_CAP,
+ // The tiled background image of the thumb.
+ THUMB_MIDDLE,
+ // The bottom/right segment of the thumb on the scrollbar.
+ THUMB_END_CAP,
+ // The grippy that is rendered in the center of the thumb.
+ THUMB_GRIPPY,
+ // The tiled background image of the thumb track.
+ THUMB_TRACK,
+ PART_COUNT
+ };
+
+ // Sets the bitmap to be rendered for the specified part and state.
+ void SetImage(ScrollBarPart part,
+ CustomButton::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 gfx::Size GetPreferredSize();
+ virtual void Paint(ChromeCanvas* canvas);
+ virtual void Layout();
+ 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(Button* 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(CustomButton::ButtonState state);
+
+ // The thumb needs to be able to access the part images.
+ friend BitmapScrollBarThumb;
+ SkBitmap* images_[PART_COUNT][CustomButton::BS_COUNT];
+
+ // 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.
+ ImageButton* prev_button_;
+ ImageButton* 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).
+ CustomButton::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);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_SCROLLBAR_BITMAP_SCROLL_BAR_H_
diff --git a/views/controls/scrollbar/native_scroll_bar.cc b/views/controls/scrollbar/native_scroll_bar.cc
new file mode 100644
index 0000000..52ddd91
--- /dev/null
+++ b/views/controls/scrollbar/native_scroll_bar.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/scrollbar/native_scroll_bar.h"
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlwin.h>
+#include <atlcrack.h>
+#include <atlframe.h>
+#include <atlmisc.h>
+#include <string>
+
+#include "base/message_loop.h"
+#include "views/controls/hwnd_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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->GetWidget()->GetNativeView());
+ ::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_->width(),
+ parent_->height(),
+ 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) {
+ Widget* widget = parent_->GetWidget();
+ if (widget && widget->GetNativeView() != 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;
+ MessageLoopForUI::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) {
+ Widget* widget;
+ if (is_add && (widget = GetWidget()) && !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_)
+ sb_view_->SetBounds(GetLocalBounds(true));
+}
+
+gfx::Size NativeScrollBar::GetPreferredSize() {
+ if (IsHorizontal())
+ return gfx::Size(0, GetLayoutSize());
+ return gfx::Size(GetLayoutSize(), 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);
+}
+
+} // namespace views
diff --git a/views/controls/scrollbar/native_scroll_bar.h b/views/controls/scrollbar/native_scroll_bar.h
new file mode 100644
index 0000000..2747bce
--- /dev/null
+++ b/views/controls/scrollbar/native_scroll_bar.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SCROLLBAR_NATIVE_SCROLLBAR_H_
+#define VIEWS_CONTROLS_SCROLLBAR_NATIVE_SCROLLBAR_H_
+
+#include "build/build_config.h"
+
+#include "views/controls/scrollbar/scroll_bar.h"
+
+namespace views {
+
+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 gfx::Size GetPreferredSize();
+
+ // 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:
+#if defined(OS_WIN)
+ // The sb_view_ takes care of keeping sb_container in sync with the
+ // view hierarchy
+ HWNDView* sb_view_;
+#endif // defined(OS_WIN)
+
+ // 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_;
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_SCROLLBAR_NATIVE_SCROLLBAR_H_
diff --git a/views/controls/scrollbar/scroll_bar.cc b/views/controls/scrollbar/scroll_bar.cc
new file mode 100644
index 0000000..a475c44
--- /dev/null
+++ b/views/controls/scrollbar/scroll_bar.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/scrollbar/scroll_bar.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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;
+}
+
+} // namespace views
diff --git a/views/controls/scrollbar/scroll_bar.h b/views/controls/scrollbar/scroll_bar.h
new file mode 100644
index 0000000..36a9d2e
--- /dev/null
+++ b/views/controls/scrollbar/scroll_bar.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SCROLLBAR_SCROLLBAR_H_
+#define VIEWS_CONTROLS_SCROLLBAR_SCROLLBAR_H_
+
+#include "views/view.h"
+#include "views/event.h"
+
+namespace views {
+
+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_;
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_CONTROLS_SCROLLBAR_SCROLLBAR_H_
diff --git a/views/controls/separator.cc b/views/controls/separator.cc
new file mode 100644
index 0000000..f331ce8
--- /dev/null
+++ b/views/controls/separator.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/separator.h"
+
+#include "views/controls/hwnd_view.h"
+
+namespace views {
+
+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, width(), height(),
+ parent_container, NULL, NULL, NULL);
+}
+
+LRESULT Separator::OnNotify(int w_param, LPNMHDR l_param) {
+ return 0;
+}
+
+gfx::Size Separator::GetPreferredSize() {
+ return gfx::Size(width(), fixed_height_);
+}
+
+} // namespace views
diff --git a/views/controls/separator.h b/views/controls/separator.h
new file mode 100644
index 0000000..866beda
--- /dev/null
+++ b/views/controls/separator.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SEPARATOR_H_
+#define VIEWS_CONTROLS_SEPARATOR_H_
+
+#include "views/controls/native_control.h"
+
+namespace views {
+
+// 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 gfx::Size GetPreferredSize();
+
+ private:
+
+ DISALLOW_EVIL_CONSTRUCTORS(Separator);
+};
+
+} // namespace views
+
+#endif // #define VIEWS_CONTROLS_SEPARATOR_H_
diff --git a/views/controls/single_split_view.cc b/views/controls/single_split_view.cc
new file mode 100644
index 0000000..4558ffd
--- /dev/null
+++ b/views/controls/single_split_view.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/single_split_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "skia/ext/skia_utils_win.h"
+#include "views/background.h"
+
+namespace views {
+
+// Size of the divider in pixels.
+static const int kDividerSize = 4;
+
+SingleSplitView::SingleSplitView(View* leading, View* trailing)
+ : divider_x_(-1) {
+ AddChildView(leading);
+ AddChildView(trailing);
+ set_background(
+ views::Background::CreateSolidBackground(
+ skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE))));
+}
+
+void SingleSplitView::Layout() {
+ if (GetChildViewCount() != 2)
+ return;
+
+ View* leading = GetChildViewAt(0);
+ View* trailing = GetChildViewAt(1);
+ if (divider_x_ < 0)
+ divider_x_ = (width() - kDividerSize) / 2;
+ else
+ divider_x_ = std::min(divider_x_, width() - kDividerSize);
+ leading->SetBounds(0, 0, divider_x_, height());
+ trailing->SetBounds(divider_x_ + kDividerSize, 0,
+ width() - divider_x_ - kDividerSize, height());
+
+ SchedulePaint();
+
+ // Invoke super's implementation so that the children are layed out.
+ View::Layout();
+}
+
+gfx::Size SingleSplitView::GetPreferredSize() {
+ int width = 0;
+ int height = 0;
+ for (int i = 0; i < 2 && i < GetChildViewCount(); ++i) {
+ View* view = GetChildViewAt(i);
+ gfx::Size pref = view->GetPreferredSize();
+ width += pref.width();
+ height = std::max(height, pref.height());
+ }
+ width += kDividerSize;
+ return gfx::Size(width, height);
+}
+
+HCURSOR SingleSplitView::GetCursorForPoint(Event::EventType event_type,
+ int x,
+ int y) {
+ if (IsPointInDivider(x)) {
+ static HCURSOR resize_cursor = LoadCursor(NULL, IDC_SIZEWE);
+ return resize_cursor;
+ }
+ return NULL;
+}
+
+bool SingleSplitView::OnMousePressed(const MouseEvent& event) {
+ if (!IsPointInDivider(event.x()))
+ return false;
+ drag_info_.initial_mouse_x = event.x();
+ drag_info_.initial_divider_x = divider_x_;
+ return true;
+}
+
+bool SingleSplitView::OnMouseDragged(const MouseEvent& event) {
+ if (GetChildViewCount() < 2)
+ return false;
+
+ int delta_x = event.x() - drag_info_.initial_mouse_x;
+ if (UILayoutIsRightToLeft())
+ delta_x *= -1;
+ // Honor the minimum size when resizing.
+ int new_width = std::max(GetChildViewAt(0)->GetMinimumSize().width(),
+ drag_info_.initial_divider_x + delta_x);
+
+ // And don't let the view get bigger than our width.
+ new_width = std::min(width() - kDividerSize, new_width);
+
+ if (new_width != divider_x_) {
+ set_divider_x(new_width);
+ Layout();
+ }
+ return true;
+}
+
+void SingleSplitView::OnMouseReleased(const MouseEvent& event, bool canceled) {
+ if (GetChildViewCount() < 2)
+ return;
+
+ if (canceled && drag_info_.initial_divider_x != divider_x_) {
+ set_divider_x(drag_info_.initial_divider_x);
+ Layout();
+ }
+}
+
+bool SingleSplitView::IsPointInDivider(int x) {
+ if (GetChildViewCount() < 2)
+ return false;
+
+ int divider_relative_x =
+ x - GetChildViewAt(UILayoutIsRightToLeft() ? 1 : 0)->width();
+ return (divider_relative_x >= 0 && divider_relative_x < kDividerSize);
+}
+
+} // namespace views
diff --git a/views/controls/single_split_view.h b/views/controls/single_split_view.h
new file mode 100644
index 0000000..a531800
--- /dev/null
+++ b/views/controls/single_split_view.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_SINGLE_SPLIT_VIEW_H_
+#define VIEWS_CONTROLS_SINGLE_SPLIT_VIEW_H_
+
+#include "views/view.h"
+
+namespace views {
+
+// SingleSplitView lays out two views horizontally. A splitter exists between
+// the two views that the user can drag around to resize the views.
+class SingleSplitView : public views::View {
+ public:
+ SingleSplitView(View* leading, View* trailing);
+
+ virtual void Layout();
+
+ // SingleSplitView's preferred size is the sum of the preferred widths
+ // and the max of the heights.
+ virtual gfx::Size GetPreferredSize();
+
+ // Overriden to return a resize cursor when over the divider.
+ virtual HCURSOR GetCursorForPoint(Event::EventType event_type, int x, int y);
+
+ void set_divider_x(int divider_x) { divider_x_ = divider_x; }
+ int divider_x() { return divider_x_; }
+
+ protected:
+ virtual bool OnMousePressed(const MouseEvent& event);
+ virtual bool OnMouseDragged(const MouseEvent& event);
+ virtual void OnMouseReleased(const MouseEvent& event, bool canceled);
+
+ private:
+ // Returns true if |x| is over the divider.
+ bool IsPointInDivider(int x);
+
+ // Used to track drag info.
+ struct DragInfo {
+ // The initial coordinate of the mouse when the user started the drag.
+ int initial_mouse_x;
+ // The initial position of the divider when the user started the drag.
+ int initial_divider_x;
+ };
+
+ DragInfo drag_info_;
+
+ // Position of the divider.
+ int divider_x_;
+
+ DISALLOW_COPY_AND_ASSIGN(SingleSplitView);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_SINGLE_SPLIT_VIEW_H_
diff --git a/views/controls/tabbed_pane.cc b/views/controls/tabbed_pane.cc
new file mode 100644
index 0000000..401cb79
--- /dev/null
+++ b/views/controls/tabbed_pane.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/tabbed_pane.h"
+
+#include <vssym32.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/l10n_util_win.h"
+#include "app/resource_bundle.h"
+#include "base/gfx/native_theme.h"
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "skia/ext/skia_utils_win.h"
+#include "skia/include/SkColor.h"
+#include "views/background.h"
+#include "views/fill_layout.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+
+namespace views {
+
+// 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->width(), view->height()};
+ 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->background()) {
+ contents->set_background(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;
+ View* removed_tab = *iter;
+ tab_views_.erase(iter);
+
+ return removed_tab;
+}
+
+void TabbedPane::SelectTabAt(int index) {
+ DCHECK((index >= 0) && (index < static_cast<int>(tab_views_.size())));
+ TabCtrl_SetCurSel(tab_control_, index);
+ DoSelectTabAt(index);
+}
+
+void TabbedPane::SelectTabForContents(const View* contents) {
+ SelectTabAt(GetIndexForContents(contents));
+}
+
+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
+ // views 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 views 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, width(), height(),
+ 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 WidgetWin();
+ content_window_->Init(tab_control_, gfx::Rect(), 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->set_background(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);
+}
+
+int TabbedPane::GetIndexForContents(const View* contents) const {
+ std::vector<View*>::const_iterator i =
+ std::find(tab_views_.begin(), tab_views_.end(), contents);
+ DCHECK(i != tab_views_.end());
+ return static_cast<int>(i - tab_views_.begin());
+}
+
+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);
+}
+
+} // namespace views
diff --git a/views/controls/tabbed_pane.h b/views/controls/tabbed_pane.h
new file mode 100644
index 0000000..528a2d5
--- /dev/null
+++ b/views/controls/tabbed_pane.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TABBED_PANE_H_
+#define VIEWS_CONTROLS_TABBED_PANE_H_
+
+#include "views/controls/native_control.h"
+
+namespace views {
+
+// 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 WidgetWin;
+
+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|, which must be valid.
+ void SelectTabAt(int index);
+
+ // Selects the tab containing the specified |contents|, which must be valid.
+ void SelectTabForContents(const View* contents);
+
+ // 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);
+
+ // Returns the index of the tab containing the specified |contents|.
+ int GetIndexForContents(const View* contents) const;
+
+ 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.
+ WidgetWin* content_window_;
+
+ // The listener we notify about tab selection changes.
+ Listener* listener_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabbedPane);
+};
+
+} // namespace views
+
+#endif // #define VIEWS_CONTROLS_TABBED_PANE_H_
diff --git a/views/controls/table/group_table_view.cc b/views/controls/table/group_table_view.cc
new file mode 100644
index 0000000..5e6a155
--- /dev/null
+++ b/views/controls/table/group_table_view.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/table/group_table_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+
+namespace views {
+
+static const COLORREF kSeparatorLineColor = RGB(208, 208, 208);
+static const int kSeparatorLineThickness = 1;
+
+const char GroupTableView::kViewClassName[] = "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)) {
+ TableView::OnKeyDown(virtual_keycode);
+ 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::PrepareForSort() {
+ GroupRange range;
+ int row_count = RowCount();
+ model_index_to_range_start_map_.clear();
+ for (int model_row = 0; model_row < row_count;) {
+ model_->GetGroupRangeForItem(model_row, &range);
+ for (int range_counter = 0; range_counter < range.length; range_counter++)
+ model_index_to_range_start_map_[range_counter + model_row] = model_row;
+ model_row += range.length;
+ }
+}
+
+int GroupTableView::CompareRows(int model_row1, int model_row2) {
+ int range1 = model_index_to_range_start_map_[model_row1];
+ int range2 = model_index_to_range_start_map_[model_row2];
+ if (range1 == range2) {
+ // The two rows are in the same group, sort so that items in the same group
+ // always appear in the same order.
+ return model_row1 - model_row2;
+ }
+ // Sort by the first entry of each of the groups.
+ return TableView::CompareRows(range1, range2);
+}
+
+void GroupTableView::OnSelectedStateChanged() {
+ // 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();
+}
+
+// Draws the line separator betweens the groups.
+void GroupTableView::PostPaint(int model_row, int column, bool selected,
+ const CRect& bounds, HDC hdc) {
+ GroupRange group_range;
+ model_->GetGroupRangeForItem(model_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 (model_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 views
diff --git a/views/controls/table/group_table_view.h b/views/controls/table/group_table_view.h
new file mode 100644
index 0000000..d128759
--- /dev/null
+++ b/views/controls/table/group_table_view.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TABLE_GROUP_TABLE_VIEW_H_
+#define VIEWS_CONTROLS_TABLE_GROUP_TABLE_VIEW_H_
+
+#include "base/task.h"
+#include "views/controls/table/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 views {
+
+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();
+
+ // Extra-painting required to draw the separator line between groups.
+ virtual bool ImplementPostPaint() { return true; }
+ virtual void PostPaint(int model_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);
+
+ // Overriden to make sure rows in the same group stay grouped together.
+ virtual int CompareRows(int model_row1, int model_row2);
+
+ // Updates model_index_to_range_start_map_ from the model.
+ virtual void PrepareForSort();
+
+ 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_;
+
+ // Maps from model row to start of group.
+ std::map<int,int> model_index_to_range_start_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(GroupTableView);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TABLE_GROUP_TABLE_VIEW_H_
diff --git a/views/controls/table/table_view.cc b/views/controls/table/table_view.cc
new file mode 100644
index 0000000..f8c7303
--- /dev/null
+++ b/views/controls/table/table_view.cc
@@ -0,0 +1,1570 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/table/table_view.h"
+
+#include <algorithm>
+#include <windowsx.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/favicon_size.h"
+#include "app/gfx/icon_util.h"
+#include "app/l10n_util_win.h"
+#include "app/resource_bundle.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "skia/ext/skia_utils_win.h"
+#include "skia/include/SkBitmap.h"
+#include "skia/include/SkColorFilter.h"
+#include "views/controls/hwnd_view.h"
+
+namespace views {
+
+// Added to column width to prevent truncation.
+const int kListViewTextPadding = 15;
+// Additional column width necessary if column has icons.
+const int kListViewIconWidthAndPadding = 18;
+
+// TableModel -----------------------------------------------------------------
+
+// static
+const int TableView::kImageSize = 18;
+
+// Used for sorting.
+static Collator* collator = NULL;
+
+SkBitmap TableModel::GetIcon(int row) {
+ return SkBitmap();
+}
+
+int TableModel::CompareValues(int row1, int row2, int column_id) {
+ DCHECK(row1 >= 0 && row1 < RowCount() &&
+ row2 >= 0 && row2 < RowCount());
+ std::wstring value1 = GetText(row1, column_id);
+ std::wstring value2 = GetText(row2, column_id);
+ Collator* collator = GetCollator();
+
+ if (collator) {
+ UErrorCode compare_status = U_ZERO_ERROR;
+ UCollationResult compare_result = collator->compare(
+ static_cast<const UChar*>(value1.c_str()),
+ static_cast<int>(value1.length()),
+ static_cast<const UChar*>(value2.c_str()),
+ static_cast<int>(value2.length()),
+ compare_status);
+ DCHECK(U_SUCCESS(compare_status));
+ return compare_result;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+Collator* TableModel::GetCollator() {
+ if (!collator) {
+ UErrorCode create_status = U_ZERO_ERROR;
+ collator = Collator::createInstance(create_status);
+ if (!U_SUCCESS(create_status)) {
+ collator = NULL;
+ NOTREACHED();
+ }
+ }
+ return collator;
+}
+
+// TableView ------------------------------------------------------------------
+
+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())),
+ 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),
+ header_original_handler_(NULL),
+ original_handler_(NULL),
+ table_view_wrapper_(this),
+ custom_cell_font_(NULL),
+ content_offset_(0) {
+ 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) {
+ if (model == model_)
+ return;
+
+ if (list_view_ && model_)
+ model_->SetObserver(NULL);
+ model_ = model;
+ if (list_view_ && model_)
+ model_->SetObserver(this);
+ if (list_view_)
+ OnModelChanged();
+}
+
+void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
+ if (!sort_descriptors_.empty()) {
+ ResetColumnSortImage(sort_descriptors_[0].column_id,
+ NO_SORT);
+ }
+ sort_descriptors_ = sort_descriptors;
+ if (!sort_descriptors_.empty()) {
+ ResetColumnSortImage(
+ sort_descriptors_[0].column_id,
+ sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
+ }
+ if (!list_view_)
+ return;
+
+ // For some reason we have to turn off/on redraw, otherwise the display
+ // isn't updated when done.
+ SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
+
+ UpdateItemsLParams(0, 0);
+
+ SortItemsAndUpdateMapping();
+
+ SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
+}
+
+void TableView::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ if (!list_view_)
+ return;
+ SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
+ Layout();
+ if ((!sized_columns_ || autosize_columns_) && width() > 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 model_row) {
+ if (!list_view_)
+ return;
+
+ DCHECK(model_row >= 0 && model_row < 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.
+ int view_row = model_to_view(model_row);
+ ListView_SetItemState(list_view_, view_row, LVIS_SELECTED | LVIS_FOCUSED,
+ LVIS_SELECTED | LVIS_FOCUSED);
+
+ // Make it visible.
+ ListView_EnsureVisible(list_view_, view_row, 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 model_row, bool state) {
+ if (!list_view_)
+ return;
+
+ DCHECK(model_row >= 0 && model_row < RowCount());
+
+ ignore_listview_change_ = true;
+
+ // Select the specified item.
+ ListView_SetItemState(list_view_, model_to_view(model_row),
+ state ? LVIS_SELECTED : 0, LVIS_SELECTED);
+
+ ignore_listview_change_ = false;
+}
+
+void TableView::SetFocusOnItem(int model_row) {
+ if (!list_view_)
+ return;
+
+ DCHECK(model_row >= 0 && model_row < RowCount());
+
+ ignore_listview_change_ = true;
+
+ // Set the focus to the given item.
+ ListView_SetItemState(list_view_, model_to_view(model_row), LVIS_FOCUSED,
+ LVIS_FOCUSED);
+
+ ignore_listview_change_ = false;
+}
+
+int TableView::FirstSelectedRow() {
+ if (!list_view_)
+ return -1;
+
+ int view_row = ListView_GetNextItem(list_view_, -1, LVNI_ALL | LVIS_SELECTED);
+ return view_row == -1 ? -1 : view_to_model(view_row);
+}
+
+bool TableView::IsItemSelected(int model_row) {
+ if (!list_view_)
+ return false;
+
+ DCHECK(model_row >= 0 && model_row < RowCount());
+ return (ListView_GetItemState(list_view_, model_to_view(model_row),
+ LVIS_SELECTED) == LVIS_SELECTED);
+}
+
+bool TableView::ItemHasTheFocus(int model_row) {
+ if (!list_view_)
+ return false;
+
+ DCHECK(model_row >= 0 && model_row < RowCount());
+ return (ListView_GetItemState(list_view_, model_to_view(model_row),
+ LVIS_FOCUSED) == LVIS_FOCUSED);
+}
+
+TableView::iterator TableView::SelectionBegin() {
+ return TableView::iterator(this, LastSelectedViewIndex());
+}
+
+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);
+ length = model_->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 = model_to_view(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);
+ }
+ }
+ 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_ && 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);
+ 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;
+
+ if (start < 0 || length < 0 || start + length > RowCount()) {
+ NOTREACHED();
+ return;
+ }
+
+ SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
+
+ bool had_selection = (SelectedRowCount() > 0);
+ int old_row_count = RowCount();
+ if (start == 0 && length == RowCount()) {
+ // Everything was removed.
+ ListView_DeleteAllItems(list_view_);
+ view_to_model_.reset(NULL);
+ model_to_view_.reset(NULL);
+ } else {
+ // Only a portion of the data was removed.
+ if (is_sorted()) {
+ int new_row_count = model_->RowCount();
+ std::vector<int> view_items_to_remove;
+ view_items_to_remove.reserve(length);
+ // Iterate through the elements, updating the view_to_model_ mapping
+ // as well as collecting the rows that need to be deleted.
+ for (int i = 0, removed_count = 0; i < old_row_count; ++i) {
+ int model_index = view_to_model(i);
+ if (model_index >= start) {
+ if (model_index < start + length) {
+ // This item was removed.
+ view_items_to_remove.push_back(i);
+ model_index = -1;
+ } else {
+ model_index -= length;
+ }
+ }
+ if (model_index >= 0) {
+ view_to_model_[i - static_cast<int>(view_items_to_remove.size())] =
+ model_index;
+ }
+ }
+
+ // Update the model_to_view mapping from the updated view_to_model
+ // mapping.
+ for (int i = 0; i < new_row_count; ++i)
+ model_to_view_[view_to_model_[i]] = i;
+
+ // And finally delete the items. We do this backwards as the items were
+ // added ordered smallest to largest.
+ for (int i = length - 1; i >= 0; --i)
+ ListView_DeleteItem(list_view_, view_items_to_remove[i]);
+ } else {
+ for (int i = 0; i < length; ++i)
+ ListView_DeleteItem(list_view_, start);
+ }
+ }
+
+ SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
+
+ // If the row count goes to zero and we had a selection LVN_ITEMCHANGED isn't
+ // invoked, so we handle it here.
+ //
+ // When the model is set to NULL all the rows are removed. We don't notify
+ // the delegate in this case as setting the model to NULL is usually done as
+ // the last step before being deleted and callers shouldn't have to deal with
+ // getting a selection change when the model is being reset.
+ if (model_ && 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) {
+ // Remove the currently visible columns.
+ while (!visible_columns_.empty())
+ SetColumnVisibility(visible_columns_.front(), false);
+
+ all_columns_.clear();
+ for (std::vector<TableColumn>::const_iterator i = columns.begin();
+ i != columns.end(); ++i) {
+ AddColumn(*i);
+ }
+
+ // Remove any sort descriptors that are no longer valid.
+ SortDescriptors sort = sort_descriptors();
+ for (SortDescriptors::iterator i = sort.begin(); i != sort.end();) {
+ if (all_columns_.count(i->column_id) == 0)
+ i = sort.erase(i);
+ else
+ ++i;
+ }
+ sort_descriptors_ = sort;
+}
+
+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
+ // Widget). 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;
+}
+
+gfx::Point TableView::GetKeyboardContextMenuLocation() {
+ int first_selected = FirstSelectedRow();
+ int y = height() / 2;
+ if (first_selected != -1) {
+ RECT cell_bounds;
+ RECT client_rect;
+ if (ListView_GetItemRect(GetNativeControlHWND(), first_selected,
+ &cell_bounds, LVIR_BOUNDS) &&
+ GetClientRect(GetNativeControlHWND(), &client_rect) &&
+ cell_bounds.bottom >= 0 && cell_bounds.bottom < client_rect.bottom) {
+ y = cell_bounds.bottom;
+ }
+ }
+ gfx::Point screen_loc(0, y);
+ if (UILayoutIsRightToLeft())
+ screen_loc.set_x(width());
+ ConvertPointToScreen(this, &screen_loc);
+ return screen_loc;
+}
+
+void TableView::SetCustomColorsEnabled(bool custom_colors_enabled) {
+ custom_colors_enabled_ = custom_colors_enabled;
+}
+
+bool TableView::GetCellColors(int model_row,
+ int column,
+ ItemColor* foreground,
+ ItemColor* background,
+ LOGFONT* logfont) {
+ return false;
+}
+
+static int GetViewIndexFromMouseEvent(HWND window, LPARAM l_param) {
+ int x = GET_X_LPARAM(l_param);
+ int y = GET_Y_LPARAM(l_param);
+ LVHITTESTINFO hit_info = {0};
+ hit_info.pt.x = x;
+ hit_info.pt.y = y;
+ return ListView_HitTest(window, &hit_info);
+}
+
+// static
+LRESULT CALLBACK TableView::TableWndProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
+ TableView* table_view = reinterpret_cast<TableViewWrapper*>(
+ GetWindowLongPtr(window, GWLP_USERDATA))->table_view;
+
+ // Is the mouse down on the table?
+ static bool in_mouse_down = false;
+ // Should we select on mouse up?
+ static bool select_on_mouse_up = false;
+
+ // If the mouse is down, this is the location of the mouse down message.
+ static int mouse_down_x, mouse_down_y;
+
+ switch (message) {
+ case WM_CONTEXTMENU: {
+ // This addresses two problems seen with context menus in right to left
+ // locales:
+ // 1. The mouse coordinates in the l_param were occasionally wrong in
+ // weird ways. This is most often seen when right clicking on the
+ // list-view twice in a row.
+ // 2. Right clicking on the icon would show the scrollbar menu.
+ //
+ // As a work around this uses the position of the cursor and ignores
+ // the position supplied in the l_param.
+ if (table_view->UILayoutIsRightToLeft() &&
+ (GET_X_LPARAM(l_param) != -1 || GET_Y_LPARAM(l_param) != -1)) {
+ CPoint screen_point;
+ GetCursorPos(&screen_point);
+ CPoint table_point = screen_point;
+ CRect client_rect;
+ if (ScreenToClient(window, &table_point) &&
+ GetClientRect(window, &client_rect) &&
+ client_rect.PtInRect(table_point)) {
+ // The point is over the client area of the table, handle it ourself.
+ // But first select the row if it isn't already selected.
+ LVHITTESTINFO hit_info = {0};
+ hit_info.pt.x = table_point.x;
+ hit_info.pt.y = table_point.y;
+ int view_index = ListView_HitTest(window, &hit_info);
+ if (view_index != -1) {
+ int model_index = table_view->view_to_model(view_index);
+ if (!table_view->IsItemSelected(model_index))
+ table_view->Select(model_index);
+ }
+ table_view->OnContextMenu(screen_point);
+ return 0; // So that default processing doesn't occur.
+ }
+ }
+ // else case: default handling is fine, so break and let the default
+ // handler service the request (which will likely calls us back with
+ // OnContextMenu).
+ break;
+ }
+
+ case WM_CANCELMODE: {
+ if (in_mouse_down) {
+ in_mouse_down = false;
+ return 0;
+ }
+ break;
+ }
+
+ case WM_ERASEBKGND:
+ // We make WM_ERASEBKGND do nothing (returning 1 indicates we handled
+ // the request). We do this so that the table view doesn't flicker during
+ // resizing.
+ return 1;
+
+ case WM_PAINT: {
+ LRESULT result = CallWindowProc(table_view->original_handler_, window,
+ message, w_param, l_param);
+ table_view->PostPaint();
+ return result;
+ }
+
+ case WM_KEYDOWN: {
+ if (!table_view->single_selection_ && w_param == 'A' &&
+ GetKeyState(VK_CONTROL) < 0 && table_view->RowCount() > 0) {
+ // Select everything.
+ ListView_SetItemState(window, -1, LVIS_SELECTED, LVIS_SELECTED);
+ // And make the first row focused.
+ ListView_SetItemState(window, 0, LVIS_FOCUSED, LVIS_FOCUSED);
+ return 0;
+ } else if (w_param == VK_DELETE && table_view->table_view_observer_) {
+ table_view->table_view_observer_->OnTableViewDelete(table_view);
+ return 0;
+ }
+ // else case: fall through to default processing.
+ break;
+ }
+
+ case WM_LBUTTONDBLCLK: {
+ if (w_param == MK_LBUTTON)
+ table_view->OnDoubleClick();
+ return 0;
+ }
+
+ case WM_LBUTTONUP: {
+ if (in_mouse_down) {
+ in_mouse_down = false;
+ ReleaseCapture();
+ SetFocus(window);
+ if (select_on_mouse_up) {
+ int view_index = GetViewIndexFromMouseEvent(window, l_param);
+ if (view_index != -1)
+ table_view->Select(table_view->view_to_model(view_index));
+ }
+ return 0;
+ }
+ break;
+ }
+
+ case WM_LBUTTONDOWN: {
+ // ListView treats clicking on an area outside the text of a column as
+ // drag to select. This is confusing when the selection is shown across
+ // the whole row. For this reason we override the default handling for
+ // mouse down/move/up and treat the whole row as draggable. That is, no
+ // matter where you click in the row we'll attempt to start dragging.
+ //
+ // Only do custom mouse handling if no other mouse buttons are down.
+ if ((w_param | (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) ==
+ (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) {
+ if (in_mouse_down)
+ return 0;
+
+ int view_index = GetViewIndexFromMouseEvent(window, l_param);
+ if (view_index != -1) {
+ table_view->ignore_listview_change_ = true;
+ in_mouse_down = true;
+ select_on_mouse_up = false;
+ mouse_down_x = GET_X_LPARAM(l_param);
+ mouse_down_y = GET_Y_LPARAM(l_param);
+ int model_index = table_view->view_to_model(view_index);
+ bool select = true;
+ if (w_param & MK_CONTROL) {
+ select = false;
+ if (!table_view->IsItemSelected(model_index)) {
+ if (table_view->single_selection_) {
+ // Single selection mode and the row isn't selected, select
+ // only it.
+ table_view->Select(model_index);
+ } else {
+ // Not single selection, add this row to the selection.
+ table_view->SetSelectedState(model_index, true);
+ }
+ } else {
+ // Remove this row from the selection.
+ table_view->SetSelectedState(model_index, false);
+ }
+ ListView_SetSelectionMark(window, view_index);
+ } else if (!table_view->single_selection_ && w_param & MK_SHIFT) {
+ int mark_view_index = ListView_GetSelectionMark(window);
+ if (mark_view_index != -1) {
+ // Unselect everything.
+ ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
+ select = false;
+
+ // Select from mark to mouse down location.
+ for (int i = std::min(view_index, mark_view_index),
+ max_i = std::max(view_index, mark_view_index); i <= max_i;
+ ++i) {
+ table_view->SetSelectedState(table_view->view_to_model(i),
+ true);
+ }
+ }
+ }
+ // Make the row the user clicked on the focused row.
+ ListView_SetItemState(window, view_index, LVIS_FOCUSED,
+ LVIS_FOCUSED);
+ if (select) {
+ if (!table_view->IsItemSelected(model_index)) {
+ // Clear all.
+ ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
+ // And select the row the user clicked on.
+ table_view->SetSelectedState(model_index, true);
+ } else {
+ // The item is already selected, don't clear the state right away
+ // in case the user drags. Instead wait for mouse up, then only
+ // select the row the user clicked on.
+ select_on_mouse_up = true;
+ }
+ ListView_SetSelectionMark(window, view_index);
+ }
+ table_view->ignore_listview_change_ = false;
+ table_view->OnSelectedStateChanged();
+ SetCapture(window);
+ return 0;
+ }
+ // else case, continue on to default handler
+ }
+ break;
+ }
+
+ case WM_MOUSEMOVE: {
+ if (in_mouse_down) {
+ int x = GET_X_LPARAM(l_param);
+ int y = GET_Y_LPARAM(l_param);
+ if (View::ExceededDragThreshold(x - mouse_down_x, y - mouse_down_y)) {
+ // We're about to start drag and drop, which results in no mouse up.
+ // Release capture and reset state.
+ ReleaseCapture();
+ in_mouse_down = false;
+
+ NMLISTVIEW details;
+ memset(&details, 0, sizeof(details));
+ details.hdr.code = LVN_BEGINDRAG;
+ SendMessage(::GetParent(window), WM_NOTIFY, 0,
+ reinterpret_cast<LPARAM>(&details));
+ }
+ return 0;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ DCHECK(table_view->original_handler_);
+ return CallWindowProc(table_view->original_handler_, window, message, w_param,
+ l_param);
+}
+
+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 there's only one column and the title string is empty, don't show a
+ // header.
+ if (all_columns_.size() == 1) {
+ std::map<int, TableColumn>::const_iterator first =
+ all_columns_.begin();
+ if (first->second.title.empty())
+ style |= LVS_NOCOLUMNHEADER;
+ }
+ list_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalRTLStyle(),
+ WC_LISTVIEW,
+ L"",
+ style,
+ 0, 0, width(), height(),
+ parent_container, NULL, NULL, NULL);
+
+ // 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);
+ l10n_util::AdjustUIFontForWindow(list_view_);
+
+ // 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()));
+ }
+
+ if (model_)
+ model_->SetObserver(this);
+
+ // Add the groups.
+ if (model_ && 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 (model_)
+ UpdateListViewCache(0, model_->RowCount(), true);
+
+ if (table_type_ == ICON_AND_TEXT) {
+ HIMAGELIST image_list =
+ ImageList_Create(kImageSize, kImageSize, ILC_COLOR32, 2, 2);
+ // 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).
+ ChromeCanvas canvas(kImageSize, kImageSize, false);
+ // Make the background completely transparent.
+ canvas.drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode);
+ HICON empty_icon =
+ IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap());
+ ImageList_AddIcon(image_list, empty_icon);
+ ImageList_AddIcon(image_list, empty_icon);
+ DeleteObject(empty_icon);
+ 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);
+ }
+
+ SetWindowLongPtr(list_view_, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
+ original_handler_ =
+ win_util::SetWindowProc(list_view_, &TableView::TableWndProc);
+
+ // 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::ToggleSortOrder(int column_id) {
+ SortDescriptors sort = sort_descriptors();
+ if (!sort.empty() && sort[0].column_id == column_id) {
+ sort[0].ascending = !sort[0].ascending;
+ } else {
+ SortDescriptor descriptor(column_id, true);
+ sort.insert(sort.begin(), descriptor);
+ if (sort.size() > 2) {
+ // Only persist two sort descriptors.
+ sort.resize(2);
+ }
+ }
+ SetSortDescriptors(sort);
+}
+
+void TableView::UpdateItemsLParams(int start, int length) {
+ LVITEM item;
+ memset(&item, 0, sizeof(LVITEM));
+ item.mask = LVIF_PARAM;
+ int row_count = RowCount();
+ for (int i = 0; i < row_count; ++i) {
+ item.iItem = i;
+ int model_index = view_to_model(i);
+ if (length > 0 && model_index >= start)
+ model_index += length;
+ item.lParam = static_cast<LPARAM>(model_index);
+ ListView_SetItem(list_view_, &item);
+ }
+}
+
+void TableView::SortItemsAndUpdateMapping() {
+ if (!is_sorted()) {
+ ListView_SortItems(list_view_, &TableView::NaturalSortFunc, this);
+ view_to_model_.reset(NULL);
+ model_to_view_.reset(NULL);
+ return;
+ }
+
+ PrepareForSort();
+
+ // Sort the items.
+ ListView_SortItems(list_view_, &TableView::SortFunc, this);
+
+ // Cleanup the collator.
+ if (collator) {
+ delete collator;
+ collator = NULL;
+ }
+
+ // Update internal mapping to match how items were actually sorted.
+ int row_count = RowCount();
+ model_to_view_.reset(new int[row_count]);
+ view_to_model_.reset(new int[row_count]);
+ LVITEM item;
+ memset(&item, 0, sizeof(LVITEM));
+ item.mask = LVIF_PARAM;
+ for (int i = 0; i < row_count; ++i) {
+ item.iItem = i;
+ ListView_GetItem(list_view_, &item);
+ int model_index = static_cast<int>(item.lParam);
+ view_to_model_[i] = model_index;
+ model_to_view_[model_index] = i;
+ }
+}
+
+// static
+int CALLBACK TableView::SortFunc(LPARAM model_index_1_p,
+ LPARAM model_index_2_p,
+ LPARAM table_view_param) {
+ int model_index_1 = static_cast<int>(model_index_1_p);
+ int model_index_2 = static_cast<int>(model_index_2_p);
+ TableView* table_view = reinterpret_cast<TableView*>(table_view_param);
+ return table_view->CompareRows(model_index_1, model_index_2);
+}
+
+// static
+int CALLBACK TableView::NaturalSortFunc(LPARAM model_index_1_p,
+ LPARAM model_index_2_p,
+ LPARAM table_view_param) {
+ return model_index_1_p - model_index_2_p;
+}
+
+void TableView::ResetColumnSortImage(int column_id, SortDirection direction) {
+ if (!list_view_ || column_id == -1)
+ return;
+
+ std::vector<int>::const_iterator i =
+ std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
+ if (i == visible_columns_.end())
+ return;
+
+ HWND header = ListView_GetHeader(list_view_);
+ if (!header)
+ return;
+
+ int column_index = static_cast<int>(i - visible_columns_.begin());
+ HDITEM header_item;
+ memset(&header_item, 0, sizeof(header_item));
+ header_item.mask = HDI_FORMAT;
+ Header_GetItem(header, column_index, &header_item);
+ header_item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
+ if (direction == ASCENDING_SORT)
+ header_item.fmt |= HDF_SORTUP;
+ else if (direction == DESCENDING_SORT)
+ header_item.fmt |= HDF_SORTDOWN;
+ Header_SetItem(header, column_index, &header_item);
+}
+
+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));
+ if (is_sorted() && sort_descriptors_[0].column_id == tc.id) {
+ ResetColumnSortImage(
+ tc.id,
+ sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
+ }
+}
+
+LRESULT TableView::OnNotify(int w_param, LPNMHDR hdr) {
+ if (!model_)
+ return 0;
+
+ 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.
+ OnSelectedStateChanged();
+ }
+ 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(view_to_model(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;
+ }
+
+ case LVN_COLUMNCLICK: {
+ const TableColumn& column = GetColumnAtPosition(
+ reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem);
+ if (column.sortable)
+ ToggleSortOrder(column.id);
+ break;
+ }
+
+ case LVN_MARQUEEBEGIN: // We don't want the marque selection.
+ return 1;
+
+ 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);
+ }
+}
+
+// Returns result, unless ascending is false in which case -result is returned.
+static int SwapCompareResult(int result, bool ascending) {
+ return ascending ? result : -result;
+}
+
+int TableView::CompareRows(int model_row1, int model_row2) {
+ if (model_->HasGroups()) {
+ // By default ListView sorts the elements regardless of groups. In such
+ // a situation the groups display only the items they contain. This results
+ // in the visual order differing from the item indices. I could not find
+ // a way to iterate over the visual order in this situation. As a workaround
+ // this forces the items to be sorted by groups as well, which means the
+ // visual order matches the item indices.
+ int g1 = model_->GetGroupID(model_row1);
+ int g2 = model_->GetGroupID(model_row2);
+ if (g1 != g2)
+ return g1 - g2;
+ }
+ int sort_result = model_->CompareValues(
+ model_row1, model_row2, sort_descriptors_[0].column_id);
+ if (sort_result == 0 && sort_descriptors_.size() > 1 &&
+ sort_descriptors_[1].column_id != -1) {
+ // Try the secondary sort.
+ return SwapCompareResult(
+ model_->CompareValues(model_row1, model_row2,
+ sort_descriptors_[1].column_id),
+ sort_descriptors_[1].ascending);
+ }
+ return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
+}
+
+int TableView::GetColumnWidth(int column_id) {
+ if (!list_view_)
+ return -1;
+
+ std::vector<int>::const_iterator i =
+ std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
+ if (i == visible_columns_.end())
+ return -1;
+
+ return ListView_GetColumnWidth(
+ list_view_, static_cast<int>(i - visible_columns_.begin()));
+}
+
+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(view_to_model(
+ 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_);
+ l10n_util::AdjustUIFont(&logfont);
+ custom_cell_font_ = CreateFontIndirect(&logfont);
+ SelectObject(draw_info->nmcd.hdc, custom_cell_font_);
+ draw_info->clrText = foreground.color_is_set
+ ? skia::SkColorToCOLORREF(foreground.color)
+ : CLR_DEFAULT;
+ draw_info->clrTextBk = background.color_is_set
+ ? skia::SkColorToCOLORREF(background.color)
+ : CLR_DEFAULT;
+ return CDRF_NEWFONT;
+ }
+ }
+ return CDRF_DODEFAULT;
+ }
+ case CDDS_ITEMPOSTPAINT: {
+ DCHECK((table_type_ == ICON_AND_TEXT) || (ImplementPostPaint()));
+ int view_index = static_cast<int>(draw_info->nmcd.dwItemSpec);
+ // We get notifications for empty items, just ignore them.
+ if (view_index >= model_->RowCount())
+ return CDRF_DODEFAULT;
+ int model_index = view_to_model(view_index);
+ LRESULT r = CDRF_DODEFAULT;
+ // First let's take care of painting the right icon.
+ if (table_type_ == ICON_AND_TEXT) {
+ SkBitmap image = model_->GetIcon(model_index);
+ if (!image.isNull()) {
+ // Get the rect that holds the icon.
+ CRect icon_rect, client_rect;
+ if (ListView_GetItemRect(list_view_, view_index, &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_, view_index, LVIS_SELECTED | LVIS_DROPHILITED);
+ bool drop_highlight = ((selected & LVIS_DROPHILITED) != 0);
+ int bg_color_index;
+ if (!IsEnabled())
+ bg_color_index = COLOR_3DFACE;
+ else if (drop_highlight)
+ bg_color_index = COLOR_HIGHLIGHT;
+ 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(
+ skia::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_, view_index, &cell_rect,
+ LVIR_BOUNDS)) {
+ PostPaint(model_index, 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.
+ int width = this->width();
+ CRect native_bounds;
+ if (GetClientRect(GetNativeControlHWND(), &native_bounds) &&
+ native_bounds.Width() > 0) {
+ // Prefer the bounds of the window over our bounds, which may be different.
+ width = native_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);
+ }
+ }
+ }
+}
+
+gfx::Size TableView::GetPreferredSize() {
+ return preferred_size_;
+}
+
+void TableView::UpdateListViewCache0(int start, int length, bool add) {
+ if (is_sorted()) {
+ if (add)
+ UpdateItemsLParams(start, length);
+ else
+ UpdateItemsLParams(0, 0);
+ }
+
+ 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 (add) {
+ if (has_groups)
+ item.mask = LVIF_GROUPID;
+ item.mask |= LVIF_PARAM;
+ for (int i = start; i < max_row; ++i) {
+ item.iItem = i;
+ if (has_groups)
+ item.iGroupId = model_->GetGroupID(i);
+ item.lParam = 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 = add ? i : model_to_view(i);
+ item.pszText = const_cast<LPWSTR>(text.c_str());
+ item.state = INDEXTOSTATEIMAGEMASK(model_->IsChecked(i) ? 2 : 1);
+ ListView_SetItem(list_view_, &item);
+ }
+ }
+
+ 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 = add ? i : model_to_view(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;
+ }
+ }
+
+ if (is_sorted()) {
+ // NOTE: As most of our tables are smallish I'm not going to optimize this.
+ // If our tables become large and frequently update, then it'll make sense
+ // to optimize this.
+
+ SortItemsAndUpdateMapping();
+ }
+}
+
+void TableView::OnDoubleClick() {
+ if (!ignore_listview_change_ && table_view_observer_) {
+ table_view_observer_->OnDoubleClick();
+ }
+}
+
+void TableView::OnSelectedStateChanged() {
+ if (!ignore_listview_change_ && table_view_observer_) {
+ table_view_observer_->OnSelectionChanged();
+ }
+}
+
+void TableView::OnKeyDown(unsigned short virtual_keycode) {
+ if (!ignore_listview_change_ && table_view_observer_) {
+ table_view_observer_->OnKeyDown(virtual_keycode);
+ }
+}
+
+void TableView::OnCheckedStateChanged(int model_row, bool is_checked) {
+ if (!ignore_listview_change_)
+ model_->SetChecked(model_row, is_checked);
+}
+
+int TableView::PreviousSelectedViewIndex(int view_index) {
+ DCHECK(view_index >= 0);
+ if (!list_view_ || view_index <= 0)
+ return -1;
+
+ int row_count = RowCount();
+ if (row_count == 0)
+ return -1; // Empty table, nothing can be selected.
+
+ // For some reason
+ // ListView_GetNextItem(list_view_,item, LVNI_SELECTED | LVNI_ABOVE)
+ // fails on Vista (always returns -1), so we iterate through the indices.
+ view_index = std::min(view_index, row_count);
+ while (--view_index >= 0 && !IsItemSelected(view_to_model(view_index)));
+ return view_index;
+}
+
+int TableView::LastSelectedViewIndex() {
+ return PreviousSelectedViewIndex(RowCount());
+}
+
+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 view_index)
+ : table_view_(view),
+ view_index_(view_index) {
+ UpdateModelIndexFromViewIndex();
+}
+
+TableSelectionIterator& TableSelectionIterator::operator=(
+ const TableSelectionIterator& other) {
+ view_index_ = other.view_index_;
+ model_index_ = other.model_index_;
+ return *this;
+}
+
+bool TableSelectionIterator::operator==(const TableSelectionIterator& other) {
+ return (other.view_index_ == view_index_);
+}
+
+bool TableSelectionIterator::operator!=(const TableSelectionIterator& other) {
+ return (other.view_index_ != view_index_);
+}
+
+TableSelectionIterator& TableSelectionIterator::operator++() {
+ view_index_ = table_view_->PreviousSelectedViewIndex(view_index_);
+ UpdateModelIndexFromViewIndex();
+ return *this;
+}
+
+int TableSelectionIterator::operator*() {
+ return model_index_;
+}
+
+void TableSelectionIterator::UpdateModelIndexFromViewIndex() {
+ if (view_index_ == -1)
+ model_index_ = -1;
+ else
+ model_index_ = table_view_->view_to_model(view_index_);
+}
+
+} // namespace views
diff --git a/views/controls/table/table_view.h b/views/controls/table/table_view.h
new file mode 100644
index 0000000..8061d27
--- /dev/null
+++ b/views/controls/table/table_view.h
@@ -0,0 +1,676 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TABLE_TABLE_VIEW_H_
+#define VIEWS_CONTROLS_TABLE_TABLE_VIEW_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif // defined(OS_WIN)
+
+#include <map>
+#include <unicode/coll.h>
+#include <unicode/uchar.h>
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "skia/include/SkColor.h"
+#if defined(OS_WIN)
+// TODO(port): remove the ifdef when native_control.h is ported.
+#include "views/controls/native_control.h"
+#endif // defined(OS_WIN)
+
+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.
+//
+// Tables may be sorted either by directly invoking SetSortDescriptors or by
+// marking the column as sortable and the user doing a gesture to sort the
+// contents. TableView itself maintains the sort so that the underlying model
+// isn't effected.
+//
+// When a table is sorted the model coordinates do not necessarily match the
+// view coordinates. All table methods are in terms of the model. If you need to
+// convert to view coordinates use model_to_view.
+//
+// Sorting is done by a locale sensitive string sort. You can customize the
+// sort by way of overriding CompareValues.
+//
+// TableView is a wrapper around the window type ListView in report mode.
+namespace views {
+
+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
+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;
+
+ // Compares the values in the column with id |column_id| for the two rows.
+ // Returns a value < 0, == 0 or > 0 as to whether the first value is
+ // <, == or > the second value.
+ //
+ // This implementation does a case insensitive locale specific string
+ // comparison.
+ virtual int CompareValues(int row1, int row2, int column_id);
+
+ protected:
+ // Returns the collator used by CompareValues.
+ Collator* GetCollator();
+};
+
+// TableColumn specifies the title, alignment and size of a particular column.
+struct TableColumn {
+ enum Alignment {
+ LEFT, RIGHT, CENTER
+ };
+
+ TableColumn()
+ : id(0),
+ title(),
+ alignment(LEFT),
+ width(-1),
+ percent(),
+ min_visible_width(0),
+ sortable(false) {
+ }
+ 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),
+ sortable(false) {
+ }
+ 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),
+ sortable(false) {
+ }
+ // 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),
+ sortable(false) {
+ 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),
+ sortable(false) {
+ 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;
+
+ // Is this column sortable? Default is false
+ bool sortable;
+};
+
+// Returned from SelectionBegin/SelectionEnd
+class TableSelectionIterator {
+ public:
+ TableSelectionIterator(TableView* view, int view_index);
+ TableSelectionIterator& operator=(const TableSelectionIterator& other);
+ bool operator==(const TableSelectionIterator& other);
+ bool operator!=(const TableSelectionIterator& other);
+ TableSelectionIterator& operator++();
+ int operator*();
+
+ private:
+ void UpdateModelIndexFromViewIndex();
+
+ TableView* table_view_;
+ int view_index_;
+
+ // The index in terms of the model. This is returned from the * operator. This
+ // is cached to avoid dependencies on the view_to_model mapping.
+ int model_index_;
+};
+
+// TableViewObserver is notified about the TableView selection.
+class TableViewObserver {
+ public:
+ virtual ~TableViewObserver() {}
+
+ // Invoked when the selection changes.
+ virtual void OnSelectionChanged() = 0;
+
+ // Optional method invoked when the user double clicks on the table.
+ virtual void OnDoubleClick() {}
+
+ // Optional method invoked when the user hits a key with the table in focus.
+ virtual void OnKeyDown(unsigned short virtual_keycode) {}
+
+ // Invoked when the user presses the delete key.
+ virtual void OnTableViewDelete(TableView* table_view) {}
+};
+
+#if defined(OS_WIN)
+// TODO(port): Port TableView.
+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;
+ };
+
+ // Describes a sorted column.
+ struct SortDescriptor {
+ SortDescriptor() : column_id(-1), ascending(true) {}
+ SortDescriptor(int column_id, bool ascending)
+ : column_id(column_id),
+ ascending(ascending) { }
+
+ // ID of the sorted column.
+ int column_id;
+
+ // Is the sort ascending?
+ bool ascending;
+ };
+
+ typedef std::vector<SortDescriptor> SortDescriptors;
+
+ // 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);
+ TableModel* model() const { return model_; }
+
+ // Resorts the contents.
+ void SetSortDescriptors(const SortDescriptors& sort_descriptors);
+
+ // Current sort.
+ const SortDescriptors& sort_descriptors() const { return sort_descriptors_; }
+
+ void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& 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 model_row);
+
+ // 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 model_row, bool state);
+
+ // Sets the focus to the item at the given index.
+ void SetFocusOnItem(int model_row);
+
+ // Returns the first selected row in terms of the model.
+ int FirstSelectedRow();
+
+ // Returns true if the item at the specified index is selected.
+ bool IsItemSelected(int model_row);
+
+ // Returns true if the item at the specified index has the focus.
+ bool ItemHasTheFocus(int model_row);
+
+ // Returns an iterator over the selection. The iterator proceeds from the
+ // last index to the first.
+ //
+ // NOTE: the iterator iterates over the visual order (but returns coordinates
+ // in terms of 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;
+ }
+ TableViewObserver* observer() const { return table_view_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.
+ virtual gfx::Size GetPreferredSize();
+ void set_preferred_size(const gfx::Size& size) { preferred_size_ = size; }
+
+ // Is the table sorted?
+ bool is_sorted() const { return !sort_descriptors_.empty(); }
+
+ // Maps from the index in terms of the model to that of the view.
+ int model_to_view(int model_index) const {
+ return model_to_view_.get() ? model_to_view_[model_index] : model_index;
+ }
+
+ // Maps from the index in terms of the view to that of the model.
+ int view_to_model(int view_index) const {
+ return view_to_model_.get() ? view_to_model_[view_index] : view_index;
+ }
+
+ protected:
+ // Overriden to return the position of the first selected row.
+ virtual gfx::Point GetKeyboardContextMenuLocation();
+
+ // 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();
+
+ // Notification from the ListView that the used double clicked the table.
+ virtual void OnDoubleClick();
+
+ // Subclasses can implement this method if they need to be notified of a key
+ // press event. Other wise, it appeals to table_view_observer_
+ virtual void OnKeyDown(unsigned short virtual_keycode);
+
+ // 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 model_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 model_row, int column, bool selected,
+ const CRect& bounds, HDC device_context) { }
+ virtual void PostPaint() {}
+
+ virtual HWND CreateNativeControl(HWND parent_container);
+
+ virtual LRESULT OnNotify(int w_param, LPNMHDR l_param);
+
+ // Overriden to destroy the image list.
+ virtual void OnDestroy();
+
+ // Used to sort the two rows. Returns a value < 0, == 0 or > 0 indicating
+ // whether the row2 comes before row1, row2 is the same as row1 or row1 comes
+ // after row2. This invokes CompareValues on the model with the sorted column.
+ virtual int CompareRows(int model_row1, int model_row2);
+
+ // Called before sorting. This does nothing and is intended for subclasses
+ // that need to cache state used during sorting.
+ virtual void PrepareForSort() {}
+
+ // Returns the width of the specified column by id, or -1 if the column isn't
+ // visible.
+ int GetColumnWidth(int column_id);
+
+ // Returns the offset from the top of the client area to the start of the
+ // content.
+ int content_offset() const { return content_offset_; }
+
+ // Size (width and height) of images.
+ static const int kImageSize;
+
+ private:
+ // Direction of a sort.
+ enum SortDirection {
+ ASCENDING_SORT,
+ DESCENDING_SORT,
+ NO_SORT
+ };
+
+ // 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 {
+ explicit TableViewWrapper(TableView* view) : table_view(view) { }
+ TableView* table_view;
+ };
+
+ friend class ListViewParent;
+ friend class TableSelectionIterator;
+
+ LRESULT OnCustomDraw(NMLVCUSTOMDRAW* draw_info);
+
+ // Invoked when the user clicks on a column to toggle the sort order. If
+ // column_id is the primary sorted column the direction of the sort is
+ // toggled, otherwise column_id is made the primary sorted column.
+ void ToggleSortOrder(int column_id);
+
+ // Updates the lparam of each of the list view items to be the model index.
+ // If length is > 0, all items with an index >= start get offset by length.
+ // This is used during sorting to determine how the items were sorted.
+ void UpdateItemsLParams(int start, int length);
+
+ // Does the actual sort and updates the mappings (view_to_model and
+ // model_to_view) appropriately.
+ void SortItemsAndUpdateMapping();
+
+ // Method invoked by ListView to compare the two values. Invokes CompareRows.
+ static int CALLBACK SortFunc(LPARAM model_index_1_p,
+ LPARAM model_index_2_p,
+ LPARAM table_view_param);
+
+ // Method invoked by ListView when sorting back to natural state. Returns
+ // model_index_1_p - model_index_2_p.
+ static int CALLBACK NaturalSortFunc(LPARAM model_index_1_p,
+ LPARAM model_index_2_p,
+ LPARAM table_view_param);
+
+ // Resets the sort image displayed for the specified column.
+ void ResetColumnSortImage(int column_id, SortDirection direction);
+
+ // 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 model_row, bool is_checked);
+
+ // Returns the index of the selected item before |view_index|, or -1 if
+ // |view_index| is the first selected item.
+ //
+ // WARNING: this returns coordinates in terms of the view, NOT the model.
+ int PreviousSelectedViewIndex(int view_index);
+
+ // Returns the last selected view index in the table view, or -1 if the table
+ // is empty, or nothing is selected.
+ //
+ // WARNING: this returns coordinates in terms of the view, NOT the model.
+ int LastSelectedViewIndex();
+
+ // The TableColumn visible at position pos.
+ const TableColumn& GetColumnAtPosition(int pos);
+
+ // Window procedure of the list view class. We subclass the list view to
+ // ignore WM_ERASEBKGND, which gives smoother painting during resizing.
+ static LRESULT CALLBACK TableWndProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param);
+
+ // 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, TableColumn> all_columns_;
+
+ // Cached value of columns_.size()
+ int column_count_;
+
+ // 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's header original proc handler. It is required when
+ // subclassing.
+ WNDPROC header_original_handler_;
+
+ // Window procedure of the listview before we subclassed it.
+ WNDPROC 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.
+ gfx::Size preferred_size_;
+
+ int content_offset_;
+
+ // Current sort.
+ SortDescriptors sort_descriptors_;
+
+ // Mappings used when sorted.
+ scoped_array<int> view_to_model_;
+ scoped_array<int> model_to_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TableView);
+};
+#endif // defined(OS_WIN)
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TABLE_TABLE_VIEW_H_
diff --git a/views/controls/table/table_view_unittest.cc b/views/controls/table/table_view_unittest.cc
new file mode 100644
index 0000000..e0f08e2
--- /dev/null
+++ b/views/controls/table/table_view_unittest.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/controls/table/table_view.h"
+#include "views/window/window_delegate.h"
+#include "views/window/window_win.h"
+
+using views::TableView;
+
+// TestTableModel --------------------------------------------------------------
+
+// Trivial TableModel implementation that is backed by a vector of vectors.
+// Provides methods for adding/removing/changing the contents that notify the
+// observer appropriately.
+//
+// Initial contents are:
+// 0, 1
+// 1, 1
+// 2, 2
+class TestTableModel : public views::TableModel {
+ public:
+ TestTableModel();
+
+ // Adds a new row at index |row| with values |c1_value| and |c2_value|.
+ void AddRow(int row, int c1_value, int c2_value);
+
+ // Removes the row at index |row|.
+ void RemoveRow(int row);
+
+ // Changes the values of the row at |row|.
+ void ChangeRow(int row, int c1_value, int c2_value);
+
+ // TableModel
+ virtual int RowCount();
+ virtual std::wstring GetText(int row, int column_id);
+ virtual void SetObserver(views::TableModelObserver* observer);
+ virtual int CompareValues(int row1, int row2, int column_id);
+
+ private:
+ views::TableModelObserver* observer_;
+
+ // The data.
+ std::vector<std::vector<int>> rows_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTableModel);
+};
+
+TestTableModel::TestTableModel() : observer_(NULL) {
+ AddRow(0, 0, 1);
+ AddRow(1, 1, 1);
+ AddRow(2, 2, 2);
+}
+
+void TestTableModel::AddRow(int row, int c1_value, int c2_value) {
+ DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
+ std::vector<int> new_row;
+ new_row.push_back(c1_value);
+ new_row.push_back(c2_value);
+ rows_.insert(rows_.begin() + row, new_row);
+ if (observer_)
+ observer_->OnItemsAdded(row, 1);
+}
+void TestTableModel::RemoveRow(int row) {
+ DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
+ rows_.erase(rows_.begin() + row);
+ if (observer_)
+ observer_->OnItemsRemoved(row, 1);
+}
+
+void TestTableModel::ChangeRow(int row, int c1_value, int c2_value) {
+ DCHECK(row >= 0 && row < static_cast<int>(rows_.size()));
+ rows_[row][0] = c1_value;
+ rows_[row][1] = c2_value;
+ if (observer_)
+ observer_->OnItemsChanged(row, 1);
+}
+
+int TestTableModel::RowCount() {
+ return static_cast<int>(rows_.size());
+}
+
+std::wstring TestTableModel::GetText(int row, int column_id) {
+ return IntToWString(rows_[row][column_id]);
+}
+
+void TestTableModel::SetObserver(views::TableModelObserver* observer) {
+ observer_ = observer;
+}
+
+int TestTableModel::CompareValues(int row1, int row2, int column_id) {
+ return rows_[row1][column_id] - rows_[row2][column_id];
+}
+
+// TableViewTest ---------------------------------------------------------------
+
+class TableViewTest : public testing::Test, views::WindowDelegate {
+ public:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ virtual views::View* GetContentsView() {
+ return table_;
+ }
+
+ protected:
+ // Creates the model.
+ TestTableModel* CreateModel();
+
+ // Verifies the view order matches that of the supplied arguments. The
+ // arguments are in terms of the model. For example, values of '1, 0' indicate
+ // the model index at row 0 is 1 and the model index at row 1 is 0.
+ void VeriyViewOrder(int first, ...);
+
+ // Verifies the selection matches the supplied arguments. The supplied
+ // arguments are in terms of this model. This uses the iterator returned by
+ // SelectionBegin.
+ void VerifySelectedRows(int first, ...);
+
+ // Configures the state for the various multi-selection tests.
+ // This selects model rows 0 and 1, and if |sort| is true the first column
+ // is sorted in descending order.
+ void SetUpMultiSelectTestState(bool sort);
+
+ scoped_ptr<TestTableModel> model_;
+
+ // The table. This is owned by the window.
+ TableView* table_;
+
+ private:
+ MessageLoopForUI message_loop_;
+ views::Window* window_;
+};
+
+void TableViewTest::SetUp() {
+ OleInitialize(NULL);
+ model_.reset(CreateModel());
+ std::vector<views::TableColumn> columns;
+ columns.resize(2);
+ columns[0].id = 0;
+ columns[1].id = 1;
+ table_ = new TableView(model_.get(), columns, views::ICON_AND_TEXT,
+ false, false, false);
+ window_ =
+ views::Window::CreateChromeWindow(NULL,
+ gfx::Rect(100, 100, 512, 512),
+ this);
+}
+
+void TableViewTest::TearDown() {
+ window_->Close();
+ // Temporary workaround to avoid leak of RootView::pending_paint_task_.
+ message_loop_.RunAllPending();
+ OleUninitialize();
+}
+
+void TableViewTest::VeriyViewOrder(int first, ...) {
+ va_list marker;
+ va_start(marker, first);
+ int value = first;
+ int index = 0;
+ for (int value = first, index = 0; value != -1; index++) {
+ ASSERT_EQ(value, table_->view_to_model(index));
+ value = va_arg(marker, int);
+ }
+ va_end(marker);
+}
+
+void TableViewTest::VerifySelectedRows(int first, ...) {
+ va_list marker;
+ va_start(marker, first);
+ int value = first;
+ int index = 0;
+ TableView::iterator selection_iterator = table_->SelectionBegin();
+ for (int value = first, index = 0; value != -1; index++) {
+ ASSERT_TRUE(selection_iterator != table_->SelectionEnd());
+ ASSERT_EQ(value, *selection_iterator);
+ value = va_arg(marker, int);
+ ++selection_iterator;
+ }
+ ASSERT_TRUE(selection_iterator == table_->SelectionEnd());
+ va_end(marker);
+}
+
+void TableViewTest::SetUpMultiSelectTestState(bool sort) {
+ // Select two rows.
+ table_->SetSelectedState(0, true);
+ table_->SetSelectedState(1, true);
+
+ VerifySelectedRows(1, 0, -1);
+ if (!sort || HasFatalFailure())
+ return;
+
+ // Sort by first column descending.
+ TableView::SortDescriptors sd;
+ sd.push_back(TableView::SortDescriptor(0, false));
+ table_->SetSortDescriptors(sd);
+ VeriyViewOrder(2, 1, 0, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Make sure the two rows are sorted.
+ // NOTE: the order changed because iteration happens over view indices.
+ VerifySelectedRows(0, 1, -1);
+}
+
+TestTableModel* TableViewTest::CreateModel() {
+ return new TestTableModel();
+}
+
+// NullModelTableViewTest ------------------------------------------------------
+
+class NullModelTableViewTest : public TableViewTest {
+ protected:
+ // Creates the model.
+ TestTableModel* CreateModel() {
+ return NULL;
+ }
+};
+
+// Tests -----------------------------------------------------------------------
+
+// Tests various sorting permutations.
+TEST_F(TableViewTest, Sort) {
+ // Sort by first column descending.
+ TableView::SortDescriptors sort;
+ sort.push_back(TableView::SortDescriptor(0, false));
+ table_->SetSortDescriptors(sort);
+ VeriyViewOrder(2, 1, 0, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Sort by second column ascending, first column descending.
+ sort.clear();
+ sort.push_back(TableView::SortDescriptor(1, true));
+ sort.push_back(TableView::SortDescriptor(0, false));
+ sort[1].ascending = false;
+ table_->SetSortDescriptors(sort);
+ VeriyViewOrder(1, 0, 2, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Clear the sort.
+ table_->SetSortDescriptors(TableView::SortDescriptors());
+ VeriyViewOrder(0, 1, 2, -1);
+ if (HasFatalFailure())
+ return;
+}
+
+// Tests changing the model while sorted.
+TEST_F(TableViewTest, SortThenChange) {
+ // Sort by first column descending.
+ TableView::SortDescriptors sort;
+ sort.push_back(TableView::SortDescriptor(0, false));
+ table_->SetSortDescriptors(sort);
+ VeriyViewOrder(2, 1, 0, -1);
+ if (HasFatalFailure())
+ return;
+
+ model_->ChangeRow(0, 3, 1);
+ VeriyViewOrder(0, 2, 1, -1);
+}
+
+// Tests adding to the model while sorted.
+TEST_F(TableViewTest, AddToSorted) {
+ // Sort by first column descending.
+ TableView::SortDescriptors sort;
+ sort.push_back(TableView::SortDescriptor(0, false));
+ table_->SetSortDescriptors(sort);
+ VeriyViewOrder(2, 1, 0, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Add row so that it occurs first.
+ model_->AddRow(0, 5, -1);
+ VeriyViewOrder(0, 3, 2, 1, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Add row so that it occurs last.
+ model_->AddRow(0, -1, -1);
+ VeriyViewOrder(1, 4, 3, 2, 0, -1);
+}
+
+// Tests selection on sort.
+TEST_F(TableViewTest, PersistSelectionOnSort) {
+ // Select row 0.
+ table_->Select(0);
+
+ // Sort by first column descending.
+ TableView::SortDescriptors sort;
+ sort.push_back(TableView::SortDescriptor(0, false));
+ table_->SetSortDescriptors(sort);
+ VeriyViewOrder(2, 1, 0, -1);
+ if (HasFatalFailure())
+ return;
+
+ // Make sure 0 is still selected.
+ EXPECT_EQ(0, table_->FirstSelectedRow());
+}
+
+// Tests selection iterator with sort.
+TEST_F(TableViewTest, PersistMultiSelectionOnSort) {
+ SetUpMultiSelectTestState(true);
+}
+
+// Tests selection persists after a change when sorted with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnChangeWithSort) {
+ SetUpMultiSelectTestState(true);
+ if (HasFatalFailure())
+ return;
+
+ model_->ChangeRow(0, 3, 1);
+
+ VerifySelectedRows(1, 0, -1);
+}
+
+// Tests selection persists after a remove when sorted with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnRemoveWithSort) {
+ SetUpMultiSelectTestState(true);
+ if (HasFatalFailure())
+ return;
+
+ model_->RemoveRow(0);
+
+ VerifySelectedRows(0, -1);
+}
+
+// Tests selection persists after a add when sorted with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnAddWithSort) {
+ SetUpMultiSelectTestState(true);
+ if (HasFatalFailure())
+ return;
+
+ model_->AddRow(3, 4, 4);
+
+ VerifySelectedRows(0, 1, -1);
+}
+
+// Tests selection persists after a change with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnChange) {
+ SetUpMultiSelectTestState(false);
+ if (HasFatalFailure())
+ return;
+
+ model_->ChangeRow(0, 3, 1);
+
+ VerifySelectedRows(1, 0, -1);
+}
+
+// Tests selection persists after a remove with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnRemove) {
+ SetUpMultiSelectTestState(false);
+ if (HasFatalFailure())
+ return;
+
+ model_->RemoveRow(0);
+
+ VerifySelectedRows(0, -1);
+}
+
+// Tests selection persists after a add with iterator.
+TEST_F(TableViewTest, PersistMultiSelectionOnAdd) {
+ SetUpMultiSelectTestState(false);
+ if (HasFatalFailure())
+ return;
+
+ model_->AddRow(3, 4, 4);
+
+ VerifySelectedRows(1, 0, -1);
+}
+
+TEST_F(NullModelTableViewTest, NullModel) {
+ // There's nothing explicit to test. If there is a bug in TableView relating
+ // to a NULL model we'll crash.
+}
diff --git a/views/controls/text_field.cc b/views/controls/text_field.cc
new file mode 100644
index 0000000..2981282
--- /dev/null
+++ b/views/controls/text_field.cc
@@ -0,0 +1,1192 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/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 "app/gfx/insets.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "base/clipboard.h"
+#include "base/gfx/native_theme.h"
+#include "base/scoped_clipboard_writer.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/common/win_util.h"
+#include "grit/generated_resources.h"
+#include "skia/ext/skia_utils_win.h"
+#include "views/controls/hwnd_view.h"
+#include "views/controls/menu/menu.h"
+#include "views/focus/focus_util_win.h"
+#include "views/widget/widget.h"
+
+using gfx::NativeTheme;
+
+namespace views {
+
+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);
+ void AppendText(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);
+
+ void SetBackgroundColor(COLORREF bg_color);
+
+ // CWindowImpl
+ BEGIN_MSG_MAP(Edit)
+ MSG_WM_CHAR(OnChar)
+ MSG_WM_CONTEXTMENU(OnContextMenu)
+ MSG_WM_COPY(OnCopy)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_CUT(OnCut)
+ MSG_WM_DESTROY(OnDestroy)
+ MESSAGE_HANDLER_EX(WM_IME_CHAR, OnImeChar)
+ MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeStartComposition)
+ 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)
+ MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel)
+ 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_COPY_AND_ASSIGN(ScopedFreeze);
+ };
+
+ // message handlers
+ void OnChar(TCHAR key, UINT repeat_count, UINT flags);
+ void OnContextMenu(HWND window, const CPoint& point);
+ void OnCopy();
+ LRESULT OnCreate(CREATESTRUCT* create_struct);
+ void OnCut();
+ void OnDestroy();
+ LRESULT OnImeChar(UINT message, WPARAM wparam, LPARAM lparam);
+ LRESULT OnImeStartComposition(UINT message, WPARAM wparam, LPARAM lparam);
+ 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();
+ LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param);
+ 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_;
+
+ // The position and the length of the ongoing composition string.
+ // These values are used for removing a composition string from a search
+ // text to emulate Firefox.
+ bool ime_discard_composition_;
+ int ime_composition_start_;
+ int ime_composition_length_;
+
+ COLORREF bg_color_;
+
+ DISALLOW_COPY_AND_ASSIGN(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),
+ ime_discard_composition_(false),
+ ime_composition_start_(0),
+ ime_composition_length_(0),
+ bg_color_(0) {
+ 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_->width(), parent_->height()};
+ Create(parent_->GetWidget()->GetNativeView(), r, NULL, style, ex_style);
+
+ if (parent->GetStyle() & TextField::STYLE_LOWERCASE) {
+ DCHECK((parent->GetStyle() & TextField::STYLE_PASSWORD) == 0);
+ SetEditStyle(SES_LOWERCASE, SES_LOWERCASE);
+ }
+
+ // 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_SELECT_ALL,
+ l10n_util::GetString(IDS_SELECT_ALL));
+}
+
+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 text_to_set;
+ if (!l10n_util::AdjustStringForLocaleDirection(text, &text_to_set))
+ text_to_set = text;
+ if (parent_->GetStyle() & STYLE_LOWERCASE)
+ text_to_set = l10n_util::ToLower(text_to_set);
+ SetWindowText(text_to_set.c_str());
+}
+
+void TextField::Edit::AppendText(const std::wstring& text) {
+ int text_length = GetWindowTextLength();
+ ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length));
+ ::SendMessage(m_hWnd, EM_REPLACESEL, false,
+ reinterpret_cast<LPARAM>(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() {
+ // Select from the end to the front so that the first part of the text is
+ // always visible.
+ SetSel(GetTextLength(), 0);
+}
+
+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);
+}
+
+void TextField::Edit::SetBackgroundColor(COLORREF bg_color) {
+ CRichEditCtrl::SetBackgroundColor(bg_color);
+ bg_color_ = bg_color;
+}
+
+bool TextField::Edit::IsCommandEnabled(int id) const {
+ switch (id) {
+ case IDS_UNDO: return !parent_->IsReadOnly() && !!CanUndo();
+ case IDS_CUT: return !parent_->IsReadOnly() &&
+ !parent_->IsPassword() && !!CanCut();
+ case IDS_COPY: return !!CanCopy() && !parent_->IsPassword();
+ case IDS_PASTE: return !parent_->IsReadOnly() && !!CanPaste();
+ case IDS_SELECT_ALL: 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_SELECT_ALL: 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() {
+ if (parent_->IsPassword())
+ return;
+
+ const std::wstring text(GetSelectedText());
+
+ if (!text.empty()) {
+ ScopedClipboardWriter scw(g_browser_process->clipboard());
+ scw.WriteText(text);
+ }
+}
+
+LRESULT TextField::Edit::OnCreate(CREATESTRUCT* create_struct) {
+ SetMsgHandled(FALSE);
+ TRACK_HWND_CREATION(m_hWnd);
+ return 0;
+}
+
+void TextField::Edit::OnCut() {
+ if (parent_->IsReadOnly() || parent_->IsPassword())
+ return;
+
+ OnCopy();
+
+ // This replace selection will have no effect (even on the undo stack) if the
+ // current selection is empty.
+ ReplaceSel(L"", true);
+}
+
+void TextField::Edit::OnDestroy() {
+ TRACK_HWND_DESTRUCTION(m_hWnd);
+}
+
+LRESULT TextField::Edit::OnImeChar(UINT message, WPARAM wparam, LPARAM lparam) {
+ // http://crbug.com/7707: a rich-edit control may crash when it receives a
+ // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message.
+ // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR
+ // messages from being dispatched to view controls via the CallWindowProc()
+ // call.
+ return 0;
+}
+
+LRESULT TextField::Edit::OnImeStartComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // Users may press alt+shift or control+shift keys to change their keyboard
+ // layouts. So, we retrieve the input locale identifier everytime we start
+ // an IME composition.
+ int language_id = PRIMARYLANGID(GetKeyboardLayout(0));
+ ime_discard_composition_ =
+ language_id == LANG_JAPANESE || language_id == LANG_CHINESE;
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+
+ return DefWindowProc(message, wparam, lparam);
+}
+
+LRESULT TextField::Edit::OnImeComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ text_before_change_.clear();
+ LRESULT result = DefWindowProc(message, wparam, lparam);
+
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+ if (ime_discard_composition_) {
+ // Call IMM32 functions to retrieve the position and the length of the
+ // ongoing composition string and notify the OnAfterPossibleChange()
+ // function that it should discard the composition string from a search
+ // string. We should not call IMM32 functions in the function because it
+ // is called when an IME is not composing a string.
+ HIMC imm_context = ImmGetContext(m_hWnd);
+ if (imm_context) {
+ CHARRANGE selection;
+ GetSel(selection);
+ const int cursor_position =
+ ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0);
+ if (cursor_position >= 0)
+ ime_composition_start_ = selection.cpMin - cursor_position;
+
+ const int composition_size =
+ ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0);
+ if (composition_size >= 0)
+ ime_composition_length_ = composition_size / sizeof(wchar_t);
+
+ ImmReleaseContext(m_hWnd, imm_context);
+ }
+ }
+
+ 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;
+
+ case VK_PROCESSKEY:
+ // This key event is consumed by an IME.
+ // We ignore this event because an IME sends WM_IME_COMPOSITION messages
+ // when it updates the CRichEditCtrl text.
+ 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);
+}
+
+LRESULT TextField::Edit::OnMouseWheel(UINT message,
+ WPARAM w_param, LPARAM l_param) {
+ // Reroute the mouse-wheel to the window under the mouse pointer if
+ // applicable.
+ if (views::RerouteMouseWheel(m_hWnd, w_param, l_param))
+ return 0;
+ return DefWindowProc(message, w_param, l_param);;
+}
+
+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());
+
+ HBRUSH brush = CreateSolidBrush(bg_color_);
+ FillRect(hdc, &window_rect, brush);
+ DeleteObject(brush);
+
+ 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, bg_color_, 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;
+
+ Clipboard* clipboard = g_browser_process->clipboard();
+
+ if (!clipboard->IsFormatAvailable(Clipboard::GetPlainTextWFormatType()))
+ return;
+
+ std::wstring clipboard_str;
+ clipboard->ReadText(&clipboard_str);
+ if (!clipboard_str.empty()) {
+ std::wstring collapsed(CollapseWhitespace(clipboard_str, false));
+ if (parent_->GetStyle() & STYLE_LOWERCASE)
+ collapsed = l10n_util::ToLower(collapsed);
+ // Force a Paste operation to trigger OnContentsChanged, even if identical
+ // contents are pasted into the text box.
+ text_before_change_.clear();
+ 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());
+
+ TextField::Controller* controller = parent_->GetController();
+ bool handled = false;
+ if (controller) {
+ handled =
+ controller->HandleKeystroke(parent_, message, key, repeat_count, flags);
+ }
+
+ if (!handled) {
+ OnBeforePossibleChange();
+ DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
+ OnAfterPossibleChange();
+ }
+}
+
+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);
+ }
+
+ std::wstring new_text(GetText());
+ if (new_text != text_before_change_) {
+ if (ime_discard_composition_ && ime_composition_start_ >= 0 &&
+ ime_composition_length_ > 0) {
+ // A string retrieved with a GetText() call contains a string being
+ // composed by an IME. We remove the composition string from this search
+ // string.
+ new_text.erase(ime_composition_start_, ime_composition_length_);
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+ if (new_text.empty())
+ return;
+ }
+ 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.
+ PARAFORMAT2 pf2;
+ GetParaFormat(pf2);
+ // Calculation of the clipped coordinate is more complicated if the paragraph
+ // layout is RTL layout, or if there is RTL characters inside the LTR layout
+ // paragraph.
+ bool ltr_text_in_ltr_layout = true;
+ if ((pf2.wEffects & PFE_RTLPARA) ||
+ l10n_util::StringContainsStrongRTLChars(GetText())) {
+ ltr_text_in_ltr_layout = false;
+ }
+ const int length = GetTextLength();
+ RECT r;
+ GetRect(&r);
+ // The values returned by PosFromChar() seem to refer always
+ // to the left edge of the character's bounding box.
+ const LONG first_position_x = PosFromChar(0).x;
+ LONG min_x = first_position_x;
+ if (!ltr_text_in_ltr_layout) {
+ for (int i = 1; i < length; ++i)
+ min_x = std::min(min_x, PosFromChar(i).x);
+ }
+ const LONG left_bound = std::max(r.left, min_x);
+
+ // PosFromChar(length) is a phantom character past the end of the text. It is
+ // not necessarily a right bound; in RTL controls it may be a left bound. So
+ // treat it as a right bound only if it is to the right of the first
+ // character.
+ LONG right_bound = r.right;
+ LONG end_position_x = PosFromChar(length).x;
+ if (end_position_x >= first_position_x) {
+ right_bound = std::min(right_bound, end_position_x); // LTR case.
+ }
+ // 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.
+ if (x < left_bound) {
+ return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 :
+ left_bound;
+ }
+ if ((length == 0) || (x < right_bound))
+ return x;
+ 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) {
+ Widget* widget;
+
+ if (is_add && (widget = GetWidget())) {
+ // 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_);
+ UpdateEditBackgroundColor();
+ Layout();
+ }
+ } else if (!is_add && edit_ && IsWindow(edit_->m_hWnd)) {
+ edit_->SetParent(NULL);
+ }
+}
+
+void TextField::Layout() {
+ if (native_view_) {
+ native_view_->SetBounds(GetLocalBounds(true));
+ native_view_->Layout();
+ }
+}
+
+gfx::Size TextField::GetPreferredSize() {
+ gfx::Insets insets;
+ CalculateInsets(&insets);
+ return gfx::Size(font_.GetExpectedTextWidth(default_width_in_chars_) +
+ insets.width(),
+ 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::AppendText(const std::wstring& text) {
+ text_ += text;
+ if (edit_)
+ edit_->AppendText(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::IsPassword() const {
+ return GetStyle() & TextField::STYLE_PASSWORD;
+}
+
+bool TextField::IsMultiLine() const {
+ return (style_ & STYLE_MULTILINE) != 0;
+}
+
+void TextField::SetReadOnly(bool read_only) {
+ read_only_ = read_only;
+ if (edit_) {
+ edit_->SetReadOnly(read_only);
+ UpdateEditBackgroundColor();
+ }
+}
+
+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;
+ UpdateEditBackgroundColor();
+}
+
+void TextField::SetDefaultBackgroundColor() {
+ use_default_background_color_ = true;
+ UpdateEditBackgroundColor();
+}
+
+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);
+ edit_->SetEnabled(enabled);
+}
+
+bool TextField::IsFocusable() const {
+ return IsEnabled() && !IsReadOnly();
+}
+
+void TextField::AboutToRequestFocusFromTabTraversal(bool reverse) {
+ SelectAll();
+}
+
+bool TextField::ShouldLookupAccelerators(const KeyEvent& e) {
+ // TODO(hamaji): Figure out which keyboard combinations we need to add here,
+ // similar to LocationBarView::ShouldLookupAccelerators.
+ if (e.GetCharacter() == VK_BACK)
+ return false; // We'll handle BackSpace ourselves.
+
+ // We don't translate accelerators for ALT + NumPad digit, they are used for
+ // entering special characters.
+ if (!e.IsAltDown())
+ return true;
+
+ return !win_util::IsNumPadDigit(e.GetCharacter(), e.IsExtendedKey());
+}
+
+void TextField::UpdateEditBackgroundColor() {
+ if (!edit_)
+ return;
+
+ COLORREF bg_color;
+ if (!use_default_background_color_)
+ bg_color = skia::SkColorToCOLORREF(background_color_);
+ else
+ bg_color = GetSysColor(read_only_ ? COLOR_3DFACE : COLOR_WINDOW);
+ edit_->SetBackgroundColor(bg_color);
+}
+
+} // namespace views
diff --git a/views/controls/text_field.h b/views/controls/text_field.h
new file mode 100644
index 0000000..3b030ba
--- /dev/null
+++ b/views/controls/text_field.h
@@ -0,0 +1,208 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// These classes define a text field widget that can be used in the views UI
+// toolkit.
+
+#ifndef VIEWS_CONTROLS_TEXT_FIELD_H_
+#define VIEWS_CONTROLS_TEXT_FIELD_H_
+
+#include <string>
+
+#include "app/gfx/chrome_font.h"
+#include "base/basictypes.h"
+#include "views/view.h"
+#include "skia/include/SkColor.h"
+
+namespace views {
+
+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.
+ // This method returns true if the message was handled and should not be
+ // processed further. If it returns false the processing continues.
+ virtual bool 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,
+ STYLE_LOWERCASE = 1<<2
+ };
+
+ 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 gfx::Size GetPreferredSize();
+
+ // Controller accessors
+ void SetController(Controller* controller);
+ Controller* GetController() const;
+
+ void SetReadOnly(bool read_only);
+ bool IsReadOnly() const;
+
+ bool IsPassword() 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;
+
+ // Sets the text currently displayed in the text field.
+ void SetText(const std::wstring& text);
+
+ // Appends the given string to the previously-existing text in the field.
+ void AppendText(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.
+ // NOTE: this does NOT change the read only property.
+ 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();
+
+ // Resets the background color of the edit.
+ void UpdateEditBackgroundColor();
+
+ // 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_COPY_AND_ASSIGN(TextField);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TEXT_FIELD_H_
diff --git a/views/controls/throbber.cc b/views/controls/throbber.cc
new file mode 100644
index 0000000..d230c55
--- /dev/null
+++ b/views/controls/throbber.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/throbber.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/resource_bundle.h"
+#include "base/time.h"
+#include "grit/theme_resources.h"
+#include "skia/include/SkBitmap.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace views {
+
+Throbber::Throbber(int frame_time_ms,
+ bool paint_while_stopped)
+ : running_(false),
+ paint_while_stopped_(paint_while_stopped),
+ frames_(NULL),
+ frame_time_(TimeDelta::FromMilliseconds(frame_time_ms)) {
+ 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_ = Time::Now();
+
+ timer_.Start(frame_time_ - TimeDelta::FromMilliseconds(10),
+ this, &Throbber::Run);
+
+ running_ = true;
+
+ SchedulePaint(); // paint right away
+}
+
+void Throbber::Stop() {
+ if (!running_)
+ return;
+
+ timer_.Stop();
+
+ running_ = false;
+ SchedulePaint(); // Important if we're not painting while stopped
+}
+
+void Throbber::Run() {
+ DCHECK(running_);
+
+ SchedulePaint();
+}
+
+gfx::Size Throbber::GetPreferredSize() {
+ return gfx::Size(frames_->height(), frames_->height());
+}
+
+void Throbber::Paint(ChromeCanvas* canvas) {
+ if (!running_ && !paint_while_stopped_)
+ return;
+
+ const TimeDelta elapsed_time = Time::Now() - start_time_;
+ const int current_frame =
+ static_cast<int>(elapsed_time / frame_time_) % frame_count_;
+
+ 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) {
+}
+
+void SmoothedThrobber::Start() {
+ stop_timer_.Stop();
+
+ if (!running_ && !start_timer_.IsRunning()) {
+ start_timer_.Start(TimeDelta::FromMilliseconds(kStartDelay), this,
+ &SmoothedThrobber::StartDelayOver);
+ }
+}
+
+void SmoothedThrobber::StartDelayOver() {
+ Throbber::Start();
+}
+
+void SmoothedThrobber::Stop() {
+ if (!running_)
+ start_timer_.Stop();
+
+ stop_timer_.Stop();
+ stop_timer_.Start(TimeDelta::FromMilliseconds(kStopDelay), this,
+ &SmoothedThrobber::StopDelayOver);
+}
+
+void SmoothedThrobber::StopDelayOver() {
+ Throbber::Stop();
+}
+
+// Checkmark throbber ---------------------------------------------------------
+
+CheckmarkThrobber::CheckmarkThrobber()
+ : Throbber(kFrameTimeMs, false),
+ checked_(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 = (width() - checkmark_->width()) / 2;
+ int checkmark_y = (height() - 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 views
diff --git a/views/controls/throbber.h b/views/controls/throbber.h
new file mode 100644
index 0000000..bd0e67a
--- /dev/null
+++ b/views/controls/throbber.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Throbbers display an animation, usually used as a status indicator.
+
+#ifndef VIEWS_CONTROLS_THROBBER_H_
+#define VIEWS_CONTROLS_THROBBER_H_
+
+#include "base/basictypes.h"
+#include "base/time.h"
+#include "base/timer.h"
+#include "views/view.h"
+
+class SkBitmap;
+
+namespace views {
+
+class Throbber : public View {
+ 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 gfx::Size GetPreferredSize();
+ virtual void Paint(ChromeCanvas* canvas);
+
+ protected:
+ // Specifies whether the throbber is currently animating or not
+ bool running_;
+
+ private:
+ void Run();
+
+ bool paint_while_stopped_;
+ int frame_count_; // How many frames we have.
+ base::Time start_time_; // Time when Start was called.
+ SkBitmap* frames_; // Frames bitmaps.
+ base::TimeDelta frame_time_; // How long one frame is displayed.
+ base::RepeatingTimer<Throbber> timer_; // Used to schedule Run calls.
+
+ DISALLOW_COPY_AND_ASSIGN(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();
+
+ base::OneShotTimer<SmoothedThrobber> start_timer_;
+ base::OneShotTimer<SmoothedThrobber> stop_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(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 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_COPY_AND_ASSIGN(CheckmarkThrobber);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_THROBBER_H_
diff --git a/views/controls/tree/tree_model.h b/views/controls/tree/tree_model.h
new file mode 100644
index 0000000..7fec5a8
--- /dev/null
+++ b/views/controls/tree/tree_model.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TREE_TREE_MODEL_H_
+#define VIEWS_CONTROLS_TREE_TREE_MODEL_H_
+
+#include <string>
+
+#include "base/logging.h"
+
+class SkBitmap;
+
+namespace views {
+
+class TreeModel;
+
+// TreeModelNode --------------------------------------------------------------
+
+// Type of class returned from the model.
+class TreeModelNode {
+ public:
+ // Returns the title for the node.
+ virtual std::wstring GetTitle() = 0;
+};
+
+// 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 the children of |parent| have been reordered. Note, only
+ // the direct children of |parent| have been reordered, not descendants.
+ virtual void TreeNodeChildrenReordered(TreeModel* model,
+ TreeModelNode* parent) = 0;
+
+ // Notification that the contents of a node has changed.
+ virtual void TreeNodeChanged(TreeModel* model, TreeModelNode* node) = 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();
+ }
+
+ // Returns the set of icons for the nodes in the tree. You only need override
+ // this if you don't want to use the default folder icons.
+ virtual void GetIcons(std::vector<SkBitmap>* icons) {}
+
+ // Returns the index of the icon to use for |node|. Return -1 to use the
+ // default icon. The index is relative to the list of icons returned from
+ // GetIcons.
+ virtual int GetIconIndex(TreeModelNode* node) { return -1; }
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TREE_TREE_MODEL_H_
diff --git a/views/controls/tree/tree_node_iterator.h b/views/controls/tree/tree_node_iterator.h
new file mode 100644
index 0000000..76618e7
--- /dev/null
+++ b/views/controls/tree/tree_node_iterator.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TREE_TREE_NODE_ITERATOR_H_
+#define VIEWS_CONTROLS_TREE_TREE_NODE_ITERATOR_H_
+
+#include <stack>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace views {
+
+// Iterator that iterates over the descendants of a node. The iteration does
+// not include the node itself, only the descendants. The following illustrates
+// typical usage:
+// while (iterator.has_next()) {
+// Node* node = iterator.Next();
+// // do something with node.
+// }
+template <class NodeType>
+class TreeNodeIterator {
+ public:
+ explicit TreeNodeIterator(NodeType* node) {
+ if (node->GetChildCount() > 0)
+ positions_.push(Position<NodeType>(node, 0));
+ }
+
+ // Returns true if there are more descendants.
+ bool has_next() const { return !positions_.empty(); }
+
+ // Returns the next descendant.
+ NodeType* Next() {
+ if (!has_next()) {
+ NOTREACHED();
+ return NULL;
+ }
+
+ NodeType* result = positions_.top().node->GetChild(positions_.top().index);
+
+ // Make sure we don't attempt to visit result again.
+ positions_.top().index++;
+
+ // Iterate over result's children.
+ positions_.push(Position<NodeType>(result, 0));
+
+ // Advance to next position.
+ while (!positions_.empty() && positions_.top().index >=
+ positions_.top().node->GetChildCount()) {
+ positions_.pop();
+ }
+
+ return result;
+ }
+
+ private:
+ template <class PositionNodeType>
+ struct Position {
+ Position(PositionNodeType* node, int index) : node(node), index(index) {}
+ Position() : node(NULL), index(-1) {}
+
+ PositionNodeType* node;
+ int index;
+ };
+
+ std::stack<Position<NodeType> > positions_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeNodeIterator);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TREE_TREE_NODE_ITERATOR_H_
diff --git a/views/controls/tree/tree_node_iterator_unittest.cc b/views/controls/tree/tree_node_iterator_unittest.cc
new file mode 100644
index 0000000..e5353fc
--- /dev/null
+++ b/views/controls/tree/tree_node_iterator_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "views/controls/tree/tree_node_iterator.h"
+#include "views/controls/tree/tree_node_model.h"
+
+typedef testing::Test TreeNodeIteratorTest;
+
+using views::TreeNodeWithValue;
+
+TEST_F(TreeNodeIteratorTest, Test) {
+ TreeNodeWithValue<int> root;
+ root.Add(0, new TreeNodeWithValue<int>(1));
+ root.Add(1, new TreeNodeWithValue<int>(2));
+ TreeNodeWithValue<int>* f3 = new TreeNodeWithValue<int>(3);
+ root.Add(2, f3);
+ TreeNodeWithValue<int>* f4 = new TreeNodeWithValue<int>(4);
+ f3->Add(0, f4);
+ f4->Add(0, new TreeNodeWithValue<int>(5));
+
+ views::TreeNodeIterator<TreeNodeWithValue<int> > iterator(&root);
+ ASSERT_TRUE(iterator.has_next());
+ ASSERT_EQ(root.GetChild(0), iterator.Next());
+
+ ASSERT_TRUE(iterator.has_next());
+ ASSERT_EQ(root.GetChild(1), iterator.Next());
+
+ ASSERT_TRUE(iterator.has_next());
+ ASSERT_EQ(root.GetChild(2), iterator.Next());
+
+ ASSERT_TRUE(iterator.has_next());
+ ASSERT_EQ(f4, iterator.Next());
+
+ ASSERT_TRUE(iterator.has_next());
+ ASSERT_EQ(f4->GetChild(0), iterator.Next());
+
+ ASSERT_FALSE(iterator.has_next());
+}
diff --git a/views/controls/tree/tree_node_model.h b/views/controls/tree/tree_node_model.h
new file mode 100644
index 0000000..f1e1c16
--- /dev/null
+++ b/views/controls/tree/tree_node_model.h
@@ -0,0 +1,283 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TREE_TREE_NODE_MODEL_H_
+#define VIEWS_CONTROLS_TREE_TREE_NODE_MODEL_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_vector.h"
+#include "base/stl_util-inl.h"
+#include "views/controls/tree/tree_model.h"
+
+namespace views {
+
+// 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;
+ }
+
+ // Removes all the children from this node. This does NOT delete the nodes.
+ void RemoveAll() {
+ for (size_t i = 0; i < children_->size(); ++i)
+ children_[i]->parent_ = NULL;
+ children_->clear();
+ }
+
+ // 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);
+ typename std::vector<NodeType*>::iterator i =
+ std::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;
+ }
+
+ protected:
+ std::vector<NodeType*>& children() { return children_.get(); }
+
+ private:
+ // Title displayed in the tree.
+ std::wstring title_;
+
+ NodeType* parent_;
+
+ // Children.
+ ScopedVector<NodeType> children_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeNode);
+};
+
+// TreeNodeWithValue ----------------------------------------------------------
+
+template <class ValueType>
+class TreeNodeWithValue : public TreeNode< TreeNodeWithValue<ValueType> > {
+ private:
+ typedef TreeNode< TreeNodeWithValue<ValueType> > ParentType;
+
+ public:
+ TreeNodeWithValue() { }
+
+ TreeNodeWithValue(const ValueType& value)
+ : ParentType(std::wstring()), value(value) { }
+
+ TreeNodeWithValue(const std::wstring& title, const ValueType& value)
+ : ParentType(title), value(value) { }
+
+ ValueType value;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(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)
+ : root_(root),
+ observer_(NULL) {
+ }
+
+ 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);
+ NodeType* child = parent->Remove(index);
+ NotifyObserverTreeNodesRemoved(parent, index, 1);
+ return child;
+ }
+
+ 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_COPY_AND_ASSIGN(TreeNodeModel);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TREE_TREE_NODE_MODEL_H_
diff --git a/views/controls/tree/tree_view.cc b/views/controls/tree/tree_view.cc
new file mode 100644
index 0000000..08f2255
--- /dev/null
+++ b/views/controls/tree/tree_view.cc
@@ -0,0 +1,745 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/tree/tree_view.h"
+
+#include <shellapi.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/icon_util.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "app/resource_bundle.h"
+#include "base/stl_util-inl.h"
+#include "base/win_util.h"
+#include "grit/theme_resources.h"
+#include "views/focus/focus_manager.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+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),
+ drag_enabled_(false),
+ has_custom_icons_(false),
+ image_list_(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());
+ if (image_list_)
+ ImageList_Destroy(image_list_);
+}
+
+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);
+ HIMAGELIST last_image_list = image_list_;
+ image_list_ = CreateImageList();
+ TreeView_SetImageList(tree_view_, image_list_, TVSIL_NORMAL);
+ if (last_image_list)
+ ImageList_Destroy(last_image_list);
+ }
+}
+
+// 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::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);
+ }
+}
+
+bool TreeView::IsExpanded(TreeModelNode* node) {
+ TreeModelNode* parent = model_->GetParent(node);
+ if (!parent)
+ return true;
+ if (!IsExpanded(parent))
+ return false;
+ NodeDetails* details = GetNodeDetails(node);
+ return (TreeView_GetItemState(tree_view_, details->tree_item, TVIS_EXPANDED) &
+ TVIS_EXPANDED) != 0;
+}
+
+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);
+ HTREEITEM parent_tree_item = GetTreeItemForNodeDuringMutation(parent);
+ if (!parent_tree_item)
+ return;
+
+ // 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;
+ }
+}
+
+namespace {
+
+// Callback function used to compare two items. The first two args are the
+// LPARAMs of the HTREEITEMs being compared. The last arg maps from LPARAM
+// to order. This is invoked from TreeNodeChildrenReordered.
+int CALLBACK CompareTreeItems(LPARAM item1_lparam,
+ LPARAM item2_lparam,
+ LPARAM map_as_lparam) {
+ std::map<int, int>& mapping =
+ *reinterpret_cast<std::map<int, int>*>(map_as_lparam);
+ return mapping[static_cast<int>(item1_lparam)] -
+ mapping[static_cast<int>(item2_lparam)];
+}
+
+} // namespace
+
+void TreeView::TreeNodeChildrenReordered(TreeModel* model,
+ TreeModelNode* parent) {
+ DCHECK(parent);
+ if (model_->GetChildCount(parent) <= 1)
+ return;
+
+ TVSORTCB sort_details;
+ sort_details.hParent = GetTreeItemForNodeDuringMutation(parent);
+ if (!sort_details.hParent)
+ return;
+
+ std::map<int, int> lparam_to_order_map;
+ for (int i = 0; i < model_->GetChildCount(parent); ++i) {
+ TreeModelNode* node = model_->GetChild(parent, i);
+ lparam_to_order_map[GetNodeDetails(node)->id] = i;
+ }
+
+ sort_details.lpfnCompare = &CompareTreeItems;
+ sort_details.lParam = reinterpret_cast<LPARAM>(&lparam_to_order_map);
+ TreeView_SortChildrenCB(tree_view_, &sort_details, 0);
+}
+
+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);
+}
+
+gfx::Point TreeView::GetKeyboardContextMenuLocation() {
+ int y = height() / 2;
+ if (GetSelectedNode()) {
+ RECT bounds;
+ RECT client_rect;
+ if (TreeView_GetItemRect(tree_view_,
+ GetNodeDetails(GetSelectedNode())->tree_item,
+ &bounds, TRUE) &&
+ GetClientRect(tree_view_, &client_rect) &&
+ bounds.bottom >= 0 && bounds.bottom < client_rect.bottom) {
+ y = bounds.bottom;
+ }
+ }
+ gfx::Point screen_loc(0, y);
+ if (UILayoutIsRightToLeft())
+ screen_loc.set_x(width());
+ ConvertPointToScreen(this, &screen_loc);
+ return screen_loc;
+}
+
+HWND TreeView::CreateNativeControl(HWND parent_container) {
+ int style = WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS;
+ if (!drag_enabled_)
+ style |= TVS_DISABLEDRAGDROP;
+ if (editable_)
+ style |= TVS_EDITLABELS;
+ tree_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(),
+ WC_TREEVIEW,
+ L"",
+ style,
+ 0, 0, width(), height(),
+ parent_container, NULL, NULL, NULL);
+ SetWindowLongPtr(tree_view_, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(&wrapper_));
+ original_handler_ = win_util::SetWindowProc(tree_view_,
+ &TreeWndProc);
+ l10n_util::AdjustUIFontForWindow(tree_view_);
+
+ if (model_) {
+ CreateRootItems();
+ model_->SetObserver(this);
+ image_list_ = CreateImageList();
+ TreeView_SetImageList(tree_view_, image_list_, TVSIL_NORMAL);
+ }
+
+ // 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;
+ }
+
+ case TVN_KEYDOWN:
+ if (controller_) {
+ NMTVKEYDOWN* key_down_message =
+ reinterpret_cast<NMTVKEYDOWN*>(l_param);
+ controller_->OnTreeViewKeyDown(key_down_message->wVKey);
+ }
+ break;
+
+ 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_) {
+ Widget* widget = GetWidget();
+ DCHECK(widget);
+ FocusManager* fm = FocusManager::GetFocusManager(widget->GetNativeView());
+ DCHECK(fm);
+ Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code),
+ win_util::IsShiftPressed(),
+ win_util::IsCtrlPressed(),
+ win_util::IsAltPressed()));
+ fm->ProcessAccelerator(accelerator);
+ return true;
+ }
+ return false;
+}
+
+void TreeView::OnContextMenu(const CPoint& location) {
+ if (!GetContextMenuController())
+ return;
+
+ if (location.x == -1 && location.y == -1) {
+ // Let NativeControl's implementation handle keyboard gesture.
+ NativeControl::OnContextMenu(location);
+ return;
+ }
+
+ if (show_context_menu_only_when_node_selected_) {
+ if (!GetSelectedNode())
+ return;
+
+ // Make sure the mouse is over the selected node.
+ TVHITTESTINFO hit_info;
+ gfx::Point 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) {
+ return;
+ }
+ }
+ ShowContextMenu(location.x, location.y, true);
+}
+
+TreeModelNode* TreeView::GetNodeForTreeItem(HTREEITEM tree_item) {
+ NodeDetails* details = GetNodeDetailsByTreeItem(tree_item);
+ return details ? details->node : NULL;
+}
+
+HTREEITEM TreeView::GetTreeItemForNode(TreeModelNode* node) {
+ NodeDetails* details = GetNodeDetails(node);
+ return details ? details->tree_item : NULL;
+}
+
+void TreeView::DeleteRootItems() {
+ HTREEITEM root = TreeView_GetRoot(tree_view_);
+ if (root) {
+ if (root_shown_) {
+ RecursivelyDelete(GetNodeDetailsByTreeItem(root));
+ } else {
+ do {
+ RecursivelyDelete(GetNodeDetailsByTreeItem(root));
+ } while ((root = TreeView_GetRoot(tree_view_)));
+ }
+ }
+}
+
+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 | TVIF_IMAGE;
+ // Call us back for the text.
+ insert_struct.itemex.pszText = LPSTR_TEXTCALLBACK;
+ // And the number of children.
+ insert_struct.itemex.cChildren = I_CHILDRENCALLBACK;
+ // Set the index of the icons to use. These are relative to the imagelist
+ // created in CreateImageList.
+ int icon_index = model_->GetIconIndex(node);
+ if (icon_index == -1) {
+ insert_struct.itemex.iImage = 0;
+ insert_struct.itemex.iSelectedImage = 1;
+ } else {
+ // The first two images are the default ones.
+ insert_struct.itemex.iImage = icon_index + 2;
+ insert_struct.itemex.iSelectedImage = icon_index + 2;
+ }
+ 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;
+}
+
+HIMAGELIST TreeView::CreateImageList() {
+ std::vector<SkBitmap> model_images;
+ model_->GetIcons(&model_images);
+
+ bool rtl = UILayoutIsRightToLeft();
+ // Creates the default image list used for trees.
+ 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, model_images.size() + 2,
+ model_images.size() + 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);
+ for (size_t i = 0; i < model_images.size(); ++i) {
+ HICON model_icon = IconUtil::CreateHICONFromSkBitmap(model_images[i]);
+ ImageList_AddIcon(image_list, model_icon);
+ DestroyIcon(model_icon);
+ }
+ }
+ return image_list;
+}
+
+HTREEITEM TreeView::GetTreeItemForNodeDuringMutation(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 NULL;
+ }
+ if (!root_shown_ || node != model_->GetRoot()) {
+ const NodeDetails* details = GetNodeDetails(node);
+ if (!details->loaded_children)
+ return NULL;
+ return details->tree_item;
+ }
+ return TreeView_GetRoot(tree_view_);
+}
+
+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;
+
+ // We handle the messages WM_ERASEBKGND and WM_PAINT such that we paint into
+ // a DIB first and then perform a BitBlt from the DIB into the underlying
+ // window's DC. This double buffering code prevents the tree view from
+ // flickering during resize.
+ switch (message) {
+ case WM_ERASEBKGND:
+ return 1;
+
+ case WM_PAINT: {
+ ChromeCanvasPaint canvas(window);
+ if (canvas.isEmpty())
+ return 0;
+
+ HDC dc = canvas.beginPlatformPaint();
+ if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) {
+ // ChromeCanvas ends up configuring the DC with a mode of GM_ADVANCED.
+ // For some reason a graphics mode of ADVANCED triggers all the text
+ // to be mirrored when RTL. Set the mode back to COMPATIBLE and
+ // explicitly set the layout. Additionally SetWorldTransform and
+ // COMPATIBLE don't play nicely together. We need to use
+ // SetViewportOrgEx when using a mode of COMPATIBLE.
+ //
+ // Reset the transform to the identify transform. Even though
+ // SetWorldTransform and COMPATIBLE don't play nicely, bits of the
+ // transform still carry over when we set the mode.
+ XFORM xform = {0};
+ xform.eM11 = xform.eM22 = 1;
+ SetWorldTransform(dc, &xform);
+
+ // Set the mode and layout.
+ SetGraphicsMode(dc, GM_COMPATIBLE);
+ SetLayout(dc, LAYOUT_RTL);
+
+ // Transform the viewport such that the origin of the dc is that of
+ // the dirty region. This way when we invoke WM_PRINTCLIENT tree-view
+ // draws the dirty region at the origin of the DC so that when we
+ // copy the bits everything lines up nicely. Without this we end up
+ // copying the upper-left corner to the redraw region.
+ SetViewportOrgEx(dc, -canvas.paintStruct().rcPaint.left,
+ -canvas.paintStruct().rcPaint.top, NULL);
+ }
+ SendMessage(window, WM_PRINTCLIENT, reinterpret_cast<WPARAM>(dc), 0);
+ if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) {
+ // Reset the origin of the dc back to 0. This way when we copy the bits
+ // over we copy the right bits.
+ SetViewportOrgEx(dc, 0, 0, NULL);
+ }
+ canvas.endPlatformPaint();
+ return 0;
+ }
+
+ case WM_RBUTTONDOWN:
+ if (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.
+ break;
+ }
+ WNDPROC handler = tree->original_handler_;
+ DCHECK(handler);
+ return CallWindowProc(handler, window, message, w_param, l_param);
+}
+
+} // namespace views
diff --git a/views/controls/tree/tree_view.h b/views/controls/tree/tree_view.h
new file mode 100644
index 0000000..1ded058
--- /dev/null
+++ b/views/controls/tree/tree_view.h
@@ -0,0 +1,305 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TREE_TREE_VIEW_H_
+#define VIEWS_CONTROLS_TREE_TREE_VIEW_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "views/controls/native_control.h"
+#include "views/controls/tree/tree_model.h"
+
+namespace views {
+
+class TreeView;
+
+// 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;
+ }
+
+ // Invoked when a key is pressed on the tree view.
+ virtual void OnTreeViewKeyDown(unsigned short virtual_keycode) {}
+};
+
+// 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, TreeModelObserver {
+ public:
+ TreeView();
+ virtual ~TreeView();
+
+ // Is dragging enabled? The default is false.
+ void set_drag_enabled(bool drag_enabled) { drag_enabled_ = drag_enabled; }
+ bool drag_enabled() const { return drag_enabled_; }
+
+ // Sets the model. TreeView does not take ownership of the model.
+ void SetModel(TreeModel* model);
+ TreeModel* model() const { return 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();
+
+ // Invoked from ExpandAll(). Expands the supplied node and recursively
+ // invokes itself with all children.
+ void ExpandAll(TreeModelNode* node);
+
+ // Returns true if the specified node is expanded.
+ bool IsExpanded(TreeModelNode* node);
+
+ // 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 TreeNodeChildrenReordered(TreeModel* model,
+ TreeModelNode* parent);
+ 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:
+ // Overriden to return a location based on the selected node.
+ virtual gfx::Point GetKeyboardContextMenuLocation();
+
+ // 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);
+
+ // Returns the TreeModelNode for |tree_item|.
+ TreeModelNode* GetNodeForTreeItem(HTREEITEM tree_item);
+
+ // Returns the tree item for |node|.
+ HTREEITEM GetTreeItemForNode(TreeModelNode* node);
+
+ 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;
+ };
+
+ // 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);
+
+ // Creates the image list to use for the tree.
+ HIMAGELIST CreateImageList();
+
+ // Returns the HTREEITEM for |node|. This is intended to be called when a
+ // model mutation event occur with |node| as the parent. This returns null
+ // if the user has never expanded |node| or all of its parents.
+ HTREEITEM GetTreeItemForNodeDuringMutation(TreeModelNode* node);
+
+ // 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_;
+
+ bool drag_enabled_;
+
+ // Did the model return a non-empty set of icons from GetIcons?
+ bool has_custom_icons_;
+
+ HIMAGELIST image_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeView);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TREE_TREE_VIEW_H_
diff --git a/views/event.cc b/views/event.cc
new file mode 100644
index 0000000..8e6a0f1
--- /dev/null
+++ b/views/event.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/event.h"
+
+#include "views/view.h"
+
+namespace views {
+
+Event::Event(EventType type, int flags)
+ : type_(type),
+#if defined(OS_WIN)
+ time_stamp_(GetTickCount()),
+#else
+ time_stamp_(0),
+#endif
+ flags_(flags) {
+}
+
+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) {
+}
+
+} // namespace views
diff --git a/views/event.h b/views/event.h
new file mode 100644
index 0000000..f0ab24a
--- /dev/null
+++ b/views/event.h
@@ -0,0 +1,324 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_EVENT_H_
+#define VIEWS_EVENT_H_
+
+#include "base/basictypes.h"
+
+#if defined(OS_LINUX)
+#include <gdk/gdk.h>
+#endif
+
+#include "base/gfx/point.h"
+
+class OSExchangeData;
+
+namespace views {
+
+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_;
+ }
+
+ void set_flags(int flags) {
+ flags_ = 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;
+ }
+
+#if defined(OS_WIN)
+ // Returns the EventFlags in terms of windows flags.
+ int GetWindowsFlags() const;
+
+ // Convert windows flags to views::Event flags
+ static int ConvertWindowsFlags(uint32 win_flags);
+#elif defined(OS_LINUX)
+ // Convert the state member on a GdkEvent to views::Event flags
+ static int GetFlagsFromGdkState(int state);
+#endif
+
+ protected:
+ Event(EventType type, int 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 generic 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 x() const {
+ return location_.x();
+ }
+
+ // Returns the Y location.
+ int y() const {
+ return location_.y();
+ }
+
+ // Returns the location.
+ const gfx::Point& location() const {
+ return location_;
+ }
+
+ 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,
+ EF_IS_NON_CLIENT = 1 << 17
+ };
+
+ // 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:
+#if defined(OS_WIN)
+ // Create a new key event
+ KeyEvent(EventType type, int ch, int repeat_count, int message_flags);
+#elif defined(OS_LINUX)
+ KeyEvent(GdkEventKey* event);
+#endif
+
+ int GetCharacter() const {
+ return character_;
+ }
+
+#if defined(OS_WIN)
+ bool IsExtendedKey() const;
+#endif
+
+ int GetRepeatCount() const {
+ return repeat_count_;
+ }
+
+ private:
+#if defined(OS_WIN)
+ int GetKeyStateFlags() const;
+#endif
+
+ 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 views
+
+#endif // VIEWS_EVENT_H_
diff --git a/views/event_gtk.cc b/views/event_gtk.cc
new file mode 100644
index 0000000..ac5a2ce
--- /dev/null
+++ b/views/event_gtk.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/event.h"
+
+namespace views {
+
+KeyEvent::KeyEvent(GdkEventKey* event)
+ : Event(event->type == GDK_KEY_PRESS ?
+ Event::ET_KEY_PRESSED : Event::ET_KEY_RELEASED,
+ GetFlagsFromGdkState(event->state)),
+ // TODO(erg): All these values are iffy.
+ character_(event->keyval),
+ repeat_count_(0),
+ message_flags_(0) {
+}
+
+int Event::GetFlagsFromGdkState(int state) {
+ int flags = 0;
+ if (state & GDK_CONTROL_MASK)
+ flags |= Event::EF_CONTROL_DOWN;
+ if (state & GDK_SHIFT_MASK)
+ flags |= Event::EF_SHIFT_DOWN;
+ if (state & GDK_MOD1_MASK)
+ flags |= Event::EF_ALT_DOWN;
+ if (state & GDK_BUTTON1_MASK)
+ flags |= Event::EF_LEFT_BUTTON_DOWN;
+ if (state & GDK_BUTTON2_MASK)
+ flags |= Event::EF_MIDDLE_BUTTON_DOWN;
+ if (state & GDK_BUTTON3_MASK)
+ flags |= Event::EF_RIGHT_BUTTON_DOWN;
+ return flags;
+}
+
+} // namespace views
diff --git a/views/event_win.cc b/views/event_win.cc
new file mode 100644
index 0000000..28d1330
--- /dev/null
+++ b/views/event_win.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/event.h"
+
+#include <windows.h>
+
+namespace views {
+
+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;
+}
+
+KeyEvent::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 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;
+}
+
+bool KeyEvent::IsExtendedKey() const {
+ return (message_flags_ & KF_EXTENDED) == KF_EXTENDED;
+}
+
+} // namespace views
diff --git a/views/fill_layout.cc b/views/fill_layout.cc
new file mode 100644
index 0000000..7bd1430a
--- /dev/null
+++ b/views/fill_layout.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/fill_layout.h"
+
+#include "base/logging.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// FillLayout
+
+FillLayout::FillLayout() {
+}
+
+FillLayout::~FillLayout() {
+}
+
+void FillLayout::Layout(View* host) {
+ if (host->GetChildViewCount() == 0)
+ return;
+
+ View* frame_view = host->GetChildViewAt(0);
+ frame_view->SetBounds(0, 0, host->width(), host->height());
+}
+
+gfx::Size FillLayout::GetPreferredSize(View* host) {
+ DCHECK(host->GetChildViewCount() == 1);
+ return host->GetChildViewAt(0)->GetPreferredSize();
+}
+
+} // namespace views
diff --git a/views/fill_layout.h b/views/fill_layout.h
new file mode 100644
index 0000000..e47a639
--- /dev/null
+++ b/views/fill_layout.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_FILL_LAYOUT_H_
+#define VIEWS_FILL_LAYOUT_H_
+
+#include "views/layout_manager.h"
+#include "views/view.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 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 gfx::Size GetPreferredSize(View* host);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(FillLayout);
+};
+
+} // namespace views
+
+#endif // VIEWS_FILL_LAYOUT_H_
diff --git a/views/focus/external_focus_tracker.cc b/views/focus/external_focus_tracker.cc
new file mode 100644
index 0000000..8f8bfdf
--- /dev/null
+++ b/views/focus/external_focus_tracker.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/focus/external_focus_tracker.h"
+
+#include "views/view.h"
+#include "views/focus/view_storage.h"
+
+namespace views {
+
+ExternalFocusTracker::ExternalFocusTracker(View* parent_view,
+ FocusManager* focus_manager)
+ : focus_manager_(focus_manager),
+ parent_view_(parent_view) {
+ view_storage_ = ViewStorage::GetSharedInstance();
+ last_focused_view_storage_id_ = view_storage_->CreateStorageID();
+ // Store the view which is focused when we're created.
+ StartTracking();
+}
+
+ExternalFocusTracker::~ExternalFocusTracker() {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+}
+
+void ExternalFocusTracker::FocusWillChange(View* focused_before,
+ View* focused_now) {
+ if (focused_now && !parent_view_->IsParentOf(focused_now) &&
+ parent_view_ != focused_now) {
+ // Store the newly focused view.
+ StoreLastFocusedView(focused_now);
+ }
+}
+
+void ExternalFocusTracker::FocusLastFocusedExternalView() {
+ View* last_focused_view =
+ view_storage_->RetrieveView(last_focused_view_storage_id_);
+ if (last_focused_view)
+ last_focused_view->RequestFocus();
+}
+
+void ExternalFocusTracker::SetFocusManager(FocusManager* focus_manager) {
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+ focus_manager_ = focus_manager;
+ if (focus_manager_)
+ StartTracking();
+}
+
+void ExternalFocusTracker::StoreLastFocusedView(View* view) {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ // If the view is NULL, remove the last focused view from storage, but don't
+ // try to store NULL.
+ if (view != NULL)
+ view_storage_->StoreView(last_focused_view_storage_id_, view);
+}
+
+void ExternalFocusTracker::StartTracking() {
+ StoreLastFocusedView(focus_manager_->GetFocusedView());
+ focus_manager_->AddFocusChangeListener(this);
+}
+
+} // namespace views
diff --git a/views/focus/external_focus_tracker.h b/views/focus/external_focus_tracker.h
new file mode 100644
index 0000000..22e9b7e
--- /dev/null
+++ b/views/focus/external_focus_tracker.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+#define VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+class View;
+class ViewStorage;
+
+// ExternalFocusTracker tracks the last focused view which belongs to the
+// provided focus manager and is not either the provided parent view or one of
+// its descendants. This is generally used if the parent view want to return
+// focus to some other view once it is dismissed. The parent view and the focus
+// manager must exist for the duration of the tracking. If the focus manager
+// must be deleted before this object is deleted, make sure to call
+// SetFocusManager(NULL) first.
+//
+// Typical use: When a view is added to the view hierarchy, it instantiates an
+// ExternalFocusTracker and passes in itself and its focus manager. Then,
+// when that view wants to return focus to the last focused view which is not
+// itself and not a descandant of itself, (usually when it is being closed)
+// it calls FocusLastFocusedExternalView.
+class ExternalFocusTracker : public FocusChangeListener {
+ public:
+ ExternalFocusTracker(View* parent_view, FocusManager* focus_manager);
+
+ virtual ~ExternalFocusTracker();
+ // FocusChangeListener implementation.
+ virtual void FocusWillChange(View* focused_before, View* focused_now);
+
+ // Focuses last focused view which is not a child of parent view and is not
+ // parent view itself. Returns true if focus for a view was requested, false
+ // otherwise.
+ void FocusLastFocusedExternalView();
+
+ // Sets the focus manager whose focus we are tracking. |focus_manager| can
+ // be NULL, but no focus changes will be tracked. This is useful if the focus
+ // manager went away, but you might later want to start tracking with a new
+ // manager later, or call FocusLastFocusedExternalView to focus the previous
+ // view.
+ void SetFocusManager(FocusManager* focus_manager);
+
+ private:
+ // Store the provided view. This view will be focused when
+ // FocusLastFocusedExternalView is called.
+ void StoreLastFocusedView(View* view);
+
+ // Store the currently focused view for our view manager and register as a
+ // listener for future focus changes.
+ void StartTracking();
+
+ // Focus manager which we are a listener for.
+ FocusManager* focus_manager_;
+
+ // ID of the last focused view, which we store in view_storage_.
+ int last_focused_view_storage_id_;
+
+ // Used to store the last focused view which is not a child of
+ // ExternalFocusTracker.
+ ViewStorage* view_storage_;
+
+ // The parent view of views which we should not track focus changes to. We
+ // also do not track changes to parent_view_ itself.
+ View* parent_view_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ExternalFocusTracker);
+};
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc
new file mode 100644
index 0000000..5653e7a
--- /dev/null
+++ b/views/focus/focus_manager.cc
@@ -0,0 +1,716 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "chrome/browser/renderer_host/render_widget_host_view_win.h"
+#include "views/accelerator.h"
+#include "views/focus/focus_manager.h"
+#include "views/focus/view_storage.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+// The following keys are used in SetProp/GetProp to associate additional
+// information needed for focus tracking with a window.
+
+// Maps to the FocusManager instance for a top level window. See
+// CreateFocusManager/DestoryFocusManager for usage.
+static const wchar_t* const kFocusManagerKey = L"__VIEW_CONTAINER__";
+
+// Maps to the View associated with a window.
+// We register views with window so we can:
+// - keep in sync the native focus with the view focus (when the native
+// component gets the focus, we get the WM_SETFOCUS event and we can focus the
+// associated view).
+// - prevent tab key events from being sent to views.
+static const wchar_t* const kViewKey = L"__CHROME_VIEW__";
+
+// A property set to 1 to indicate whether the focus manager has subclassed that
+// window. We are doing this to ensure we are not subclassing several times.
+// Subclassing twice is not a problem if no one is subclassing the HWND between
+// the 2 subclassings (the 2nd subclassing is ignored since the WinProc is the
+// same as the current one). However if some other app goes and subclasses the
+// HWND between the 2 subclassings, we will end up subclassing twice.
+// This flag lets us test that whether we have or not subclassed yet.
+static const wchar_t* const kFocusSubclassInstalled =
+ L"__FOCUS_SUBCLASS_INSTALLED__";
+
+namespace views {
+
+// Callback installed via InstallFocusSubclass.
+static LRESULT CALLBACK FocusWindowCallback(HWND window, UINT message,
+ WPARAM wParam, LPARAM lParam) {
+ if (!::IsWindow(window)) {
+ // QEMU has reported crashes when calling GetProp (this seems to happen for
+ // some weird messages, not sure what they are).
+ // Here we are just trying to avoid the crasher.
+ NOTREACHED();
+ return 0;
+ }
+
+ WNDPROC original_handler = win_util::GetSuperclassWNDPROC(window);
+ DCHECK(original_handler);
+ FocusManager* focus_manager = FocusManager::GetFocusManager(window);
+ // There are cases when we have no FocusManager for the window. This happens
+ // because we subclass certain windows (such as the TabContents window)
+ // but that window may not have an associated FocusManager.
+ if (focus_manager) {
+ switch (message) {
+ case WM_SETFOCUS:
+ if (!focus_manager->OnSetFocus(window))
+ return 0;
+ break;
+ case WM_NCDESTROY:
+ if (!focus_manager->OnNCDestroy(window))
+ return 0;
+ break;
+ case WM_ACTIVATE: {
+ // We call the DefWindowProc before calling OnActivate as some of our
+ // windows need the OnActivate notifications. The default activation on
+ // the window causes it to focus the main window, and since
+ // FocusManager::OnActivate attempts to restore the focused view, it
+ // needs to be called last so the focus it is setting does not get
+ // overridden.
+ LRESULT result = CallWindowProc(original_handler, window, WM_ACTIVATE,
+ wParam, lParam);
+ if (!focus_manager->OnPostActivate(window,
+ LOWORD(wParam), HIWORD(wParam)))
+ return 0;
+ return result;
+ }
+ default:
+ break;
+ }
+ }
+ return CallWindowProc(original_handler, window, message, wParam, lParam);
+}
+
+// FocusManager -----------------------------------------------------
+
+// static
+FocusManager* FocusManager::CreateFocusManager(HWND window,
+ RootView* root_view) {
+ DCHECK(window);
+ DCHECK(root_view);
+ InstallFocusSubclass(window, NULL);
+ FocusManager* focus_manager = new FocusManager(window, root_view);
+ SetProp(window, kFocusManagerKey, focus_manager);
+
+ return focus_manager;
+}
+
+// static
+void FocusManager::InstallFocusSubclass(HWND window, View* view) {
+ DCHECK(window);
+
+ bool already_subclassed =
+ reinterpret_cast<bool>(GetProp(window,
+ kFocusSubclassInstalled));
+ if (already_subclassed &&
+ !win_util::IsSubclassed(window, &FocusWindowCallback)) {
+ NOTREACHED() << "window sub-classed by someone other than the FocusManager";
+ // Track in UMA so we know if this case happens.
+ UMA_HISTOGRAM_COUNTS("FocusManager.MultipleSubclass", 1);
+ } else {
+ win_util::Subclass(window, &FocusWindowCallback);
+ SetProp(window, kFocusSubclassInstalled, reinterpret_cast<HANDLE>(true));
+ }
+ if (view)
+ SetProp(window, kViewKey, view);
+}
+
+void FocusManager::UninstallFocusSubclass(HWND window) {
+ DCHECK(window);
+ if (win_util::Unsubclass(window, &FocusWindowCallback)) {
+ RemoveProp(window, kViewKey);
+ RemoveProp(window, kFocusSubclassInstalled);
+ }
+}
+
+// static
+FocusManager* FocusManager::GetFocusManager(HWND window) {
+ DCHECK(window);
+
+ // In case parent windows belong to a different process, yet
+ // have the kFocusManagerKey property set, we have to be careful
+ // to also check the process id of the window we're checking.
+ DWORD window_pid = 0, current_pid = GetCurrentProcessId();
+ FocusManager* focus_manager;
+ for (focus_manager = NULL; focus_manager == NULL && IsWindow(window);
+ window = GetParent(window)) {
+ GetWindowThreadProcessId(window, &window_pid);
+ if (current_pid != window_pid)
+ break;
+ focus_manager = reinterpret_cast<FocusManager*>(
+ GetProp(window, kFocusManagerKey));
+ }
+ return focus_manager;
+}
+
+// static
+View* FocusManager::GetViewForWindow(HWND window, bool look_in_parents) {
+ DCHECK(window);
+ do {
+ View* v = reinterpret_cast<View*>(GetProp(window, kViewKey));
+ if (v)
+ return v;
+ } while (look_in_parents && (window = ::GetParent(window)));
+
+ return NULL;
+}
+
+FocusManager::FocusManager(HWND root, RootView* root_view)
+ : root_(root),
+ top_root_view_(root_view),
+ focused_view_(NULL),
+ ignore_set_focus_msg_(false) {
+ stored_focused_view_storage_id_ =
+ ViewStorage::GetSharedInstance()->CreateStorageID();
+ DCHECK(root_);
+}
+
+FocusManager::~FocusManager() {
+ // If there are still registered FocusChange listeners, chances are they were
+ // leaked so warn about them.
+ DCHECK(focus_change_listeners_.empty());
+}
+
+// Message handlers.
+bool FocusManager::OnSetFocus(HWND window) {
+ if (ignore_set_focus_msg_)
+ return true;
+
+ // Focus the view associated with that window.
+ View* v = static_cast<View*>(GetProp(window, kViewKey));
+ if (v && v->IsFocusable()) {
+ v->GetRootView()->FocusView(v);
+ } else {
+ SetFocusedView(NULL);
+ }
+
+ return true;
+}
+
+bool FocusManager::OnNCDestroy(HWND window) {
+ // Window is being destroyed, undo the subclassing.
+ FocusManager::UninstallFocusSubclass(window);
+
+ if (window == root_) {
+ // We are the top window.
+
+ DCHECK(GetProp(window, kFocusManagerKey));
+
+ // Make sure this is called on the window that was set with the
+ // FocusManager.
+ RemoveProp(window, kFocusManagerKey);
+
+ delete this;
+ }
+ return true;
+}
+
+bool FocusManager::OnKeyDown(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) {
+ DCHECK((message == WM_KEYDOWN) || (message == WM_SYSKEYDOWN));
+
+ if (!IsWindowVisible(root_)) {
+ // We got a message for a hidden window. Because WidgetWin::Close hides the
+ // window, then destroys it, it it possible to get a message after we've
+ // hidden the window. If we allow the message to be dispatched chances are
+ // we'll crash in some weird place. By returning false we make sure the
+ // message isn't dispatched.
+ return false;
+ }
+
+ // First give the registered keystroke handlers a chance a processing
+ // the message
+ // Do some basic checking to try to catch evil listeners that change the list
+ // from under us.
+ KeystrokeListenerList::size_type original_count =
+ keystroke_listeners_.size();
+ for (int i = 0; i < static_cast<int>(keystroke_listeners_.size()); i++) {
+ if (keystroke_listeners_[i]->ProcessKeyStroke(window, message, wparam,
+ lparam)) {
+ return false;
+ }
+ }
+ DCHECK_EQ(original_count, keystroke_listeners_.size())
+ << "KeystrokeListener list modified during notification";
+
+ int virtual_key_code = static_cast<int>(wparam);
+ // Intercept Tab related messages for focus traversal.
+ // Note that we don't do focus traversal if the root window is not part of the
+ // active window hierarchy as this would mean we have no focused view and
+ // would focus the first focusable view.
+ HWND active_window = ::GetActiveWindow();
+ if ((active_window == root_ || ::IsChild(active_window, root_)) &&
+ (virtual_key_code == VK_TAB) && !win_util::IsCtrlPressed()) {
+ if (!focused_view_ || !focused_view_->CanProcessTabKeyEvents()) {
+ AdvanceFocus(win_util::IsShiftPressed());
+ return false;
+ }
+ }
+
+ // Intercept arrow key messages to switch between grouped views.
+ if (focused_view_ && focused_view_->GetGroup() != -1 &&
+ (virtual_key_code == VK_UP || virtual_key_code == VK_DOWN ||
+ virtual_key_code == VK_LEFT || virtual_key_code == VK_RIGHT)) {
+ bool next = (virtual_key_code == VK_RIGHT || virtual_key_code == VK_DOWN);
+ std::vector<View*> views;
+ focused_view_->GetParent()->GetViewsWithGroup(focused_view_->GetGroup(),
+ &views);
+ std::vector<View*>::const_iterator iter = std::find(views.begin(),
+ views.end(),
+ focused_view_);
+ DCHECK(iter != views.end());
+ int index = static_cast<int>(iter - views.begin());
+ index += next ? 1 : -1;
+ if (index < 0) {
+ index = static_cast<int>(views.size()) - 1;
+ } else if (index >= static_cast<int>(views.size())) {
+ index = 0;
+ }
+ views[index]->RequestFocus();
+ return false;
+ }
+
+ int repeat_count = LOWORD(lparam);
+ int flags = HIWORD(lparam);
+ if (focused_view_ &&
+ !focused_view_->ShouldLookupAccelerators(KeyEvent(
+ Event::ET_KEY_PRESSED, virtual_key_code,
+ repeat_count, flags))) {
+ // This should not be processed as an accelerator.
+ return true;
+ }
+
+ // Process keyboard accelerators.
+ // We process accelerators here as we have no way of knowing if a HWND has
+ // really processed a key event. If the key combination matches an
+ // accelerator, the accelerator is triggered, otherwise we forward the
+ // event to the HWND.
+ Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code),
+ win_util::IsShiftPressed(),
+ win_util::IsCtrlPressed(),
+ win_util::IsAltPressed()));
+ if (ProcessAccelerator(accelerator)) {
+ // If a shortcut was activated for this keydown message, do not
+ // propagate the message further.
+ return false;
+ }
+ return true;
+}
+
+bool FocusManager::OnKeyUp(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) {
+ for (int i = 0; i < static_cast<int>(keystroke_listeners_.size()); ++i) {
+ if (keystroke_listeners_[i]->ProcessKeyStroke(window, message, wparam,
+ lparam)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FocusManager::OnPostActivate(HWND window,
+ int activation_state,
+ int minimized_state) {
+ if (WA_INACTIVE == LOWORD(activation_state)) {
+ StoreFocusedView();
+ } else {
+ RestoreFocusedView();
+ }
+ return false;
+}
+
+void FocusManager::ValidateFocusedView() {
+ if (focused_view_) {
+ if (!ContainsView(focused_view_)) {
+ focused_view_ = NULL;
+ }
+ }
+}
+
+// Tests whether a view is valid, whether it still belongs to the window
+// hierarchy of the FocusManager.
+bool FocusManager::ContainsView(View* view) {
+ DCHECK(view);
+ RootView* root_view = view->GetRootView();
+ if (!root_view)
+ return false;
+
+ Widget* widget = root_view->GetWidget();
+ if (!widget)
+ return false;
+
+ HWND window = widget->GetNativeView();
+ while (window) {
+ if (window == root_)
+ return true;
+ window = ::GetParent(window);
+ }
+ return false;
+}
+
+void FocusManager::AdvanceFocus(bool reverse) {
+ View* v = GetNextFocusableView(focused_view_, reverse, false);
+ // Note: Do not skip this next block when v == focused_view_. If the user
+ // tabs past the last focusable element in a webpage, we'll get here, and if
+ // the TabContentsContainerView is the only focusable view (possible in
+ // fullscreen mode), we need to run this block in order to cycle around to the
+ // first element on the page.
+ if (v) {
+ v->AboutToRequestFocusFromTabTraversal(reverse);
+ v->RequestFocus();
+ }
+}
+
+View* FocusManager::GetNextFocusableView(View* original_starting_view,
+ bool reverse,
+ bool dont_loop) {
+ FocusTraversable* focus_traversable = NULL;
+
+ // Let's revalidate the focused view.
+ ValidateFocusedView();
+
+ View* starting_view = NULL;
+ if (original_starting_view) {
+ // If the starting view has a focus traversable, use it.
+ // This is the case with WidgetWins for example.
+ focus_traversable = original_starting_view->GetFocusTraversable();
+
+ // Otherwise default to the root view.
+ if (!focus_traversable) {
+ focus_traversable = original_starting_view->GetRootView();
+ starting_view = original_starting_view;
+ }
+ } else {
+ focus_traversable = top_root_view_;
+ }
+
+ // Traverse the FocusTraversable tree down to find the focusable view.
+ View* v = FindFocusableView(focus_traversable, starting_view,
+ reverse, dont_loop);
+ if (v) {
+ return v;
+ } else {
+ // Let's go up in the FocusTraversable tree.
+ FocusTraversable* parent_focus_traversable =
+ focus_traversable->GetFocusTraversableParent();
+ starting_view = focus_traversable->GetFocusTraversableParentView();
+ while (parent_focus_traversable) {
+ FocusTraversable* new_focus_traversable = NULL;
+ View* new_starting_view = NULL;
+ v = parent_focus_traversable ->FindNextFocusableView(
+ starting_view, reverse, FocusTraversable::UP, dont_loop,
+ &new_focus_traversable, &new_starting_view);
+
+ if (new_focus_traversable) {
+ DCHECK(!v);
+
+ // There is a FocusTraversable, traverse it down.
+ v = FindFocusableView(new_focus_traversable, NULL, reverse, dont_loop);
+ }
+
+ if (v)
+ return v;
+
+ starting_view = focus_traversable->GetFocusTraversableParentView();
+ parent_focus_traversable =
+ parent_focus_traversable->GetFocusTraversableParent();
+ }
+
+ if (!dont_loop) {
+ // If we get here, we have reached the end of the focus hierarchy, let's
+ // loop.
+ if (reverse) {
+ // When reversing from the top, the next focusable view is at the end
+ // of the focus hierarchy.
+ return FindLastFocusableView();
+ } else {
+ // Easy, just clear the selection and press tab again.
+ if (original_starting_view) { // Make sure there was at least a view to
+ // start with, to prevent infinitely
+ // looping in empty windows.
+ // By calling with NULL as the starting view, we'll start from the
+ // top_root_view.
+ return GetNextFocusableView(NULL, false, true);
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+View* FocusManager::FindLastFocusableView() {
+ // Just walk the entire focus loop from where we're at until we reach the end.
+ View* new_focused = NULL;
+ View* last_focused = focused_view_;
+ while (new_focused = GetNextFocusableView(last_focused, false, true))
+ last_focused = new_focused;
+ return last_focused;
+}
+
+void FocusManager::SetFocusedView(View* view) {
+ if (focused_view_ != view) {
+ View* prev_focused_view = focused_view_;
+ if (focused_view_)
+ focused_view_->WillLoseFocus();
+
+ if (view)
+ view->WillGainFocus();
+
+ // Notified listeners that the focus changed.
+ FocusChangeListenerList::const_iterator iter;
+ for (iter = focus_change_listeners_.begin();
+ iter != focus_change_listeners_.end(); ++iter) {
+ (*iter)->FocusWillChange(prev_focused_view, view);
+ }
+
+ focused_view_ = view;
+
+ if (prev_focused_view)
+ prev_focused_view->SchedulePaint(); // Remove focus artifacts.
+
+ if (view) {
+ view->SchedulePaint();
+ view->Focus();
+ view->DidGainFocus();
+ }
+ }
+}
+
+void FocusManager::ClearFocus() {
+ SetFocusedView(NULL);
+ ClearHWNDFocus();
+}
+
+void FocusManager::ClearHWNDFocus() {
+ // Keep the top root window focused so we get keyboard events.
+ ignore_set_focus_msg_ = true;
+ ::SetFocus(root_);
+ ignore_set_focus_msg_ = false;
+}
+
+void FocusManager::FocusHWND(HWND hwnd) {
+ ignore_set_focus_msg_ = true;
+ // Only reset focus if hwnd is not already focused.
+ if (hwnd && ::GetFocus() != hwnd)
+ ::SetFocus(hwnd);
+ ignore_set_focus_msg_ = false;
+}
+
+void FocusManager::StoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+
+ // TODO (jcampan): when a TabContents containing a popup is closed, the focus
+ // is stored twice causing an assert. We should find a better alternative than
+ // removing the view from the storage explicitly.
+ view_storage->RemoveView(stored_focused_view_storage_id_);
+
+ if (!focused_view_)
+ return;
+
+ view_storage->StoreView(stored_focused_view_storage_id_, focused_view_);
+
+ View* v = focused_view_;
+ focused_view_ = NULL;
+
+ if (v)
+ v->SchedulePaint(); // Remove focus border.
+}
+
+void FocusManager::RestoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+
+ View* view = view_storage->RetrieveView(stored_focused_view_storage_id_);
+ if (view) {
+ if (ContainsView(view))
+ view->RequestFocus();
+ } else {
+ // Clearing the focus will focus the root window, so we still get key
+ // events.
+ ClearFocus();
+ }
+}
+
+void FocusManager::ClearStoredFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+ view_storage->RemoveView(stored_focused_view_storage_id_);
+}
+
+FocusManager* FocusManager::GetParentFocusManager() const {
+ HWND parent = ::GetParent(root_);
+ // If we are a top window, we don't have a parent FocusManager.
+ if (!parent)
+ return NULL;
+
+ return GetFocusManager(parent);
+}
+
+// Find the next (previous if reverse is true) focusable view for the specified
+// FocusTraversable, starting at the specified view, traversing down the
+// FocusTraversable hierarchy.
+View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable,
+ View* starting_view,
+ bool reverse,
+ bool dont_loop) {
+ FocusTraversable* new_focus_traversable = NULL;
+ View* new_starting_view = NULL;
+ View* v = focus_traversable->FindNextFocusableView(starting_view,
+ reverse,
+ FocusTraversable::DOWN,
+ dont_loop,
+ &new_focus_traversable,
+ &new_starting_view);
+
+ // Let's go down the FocusTraversable tree as much as we can.
+ while (new_focus_traversable) {
+ DCHECK(!v);
+ focus_traversable = new_focus_traversable;
+ starting_view = new_starting_view;
+ new_focus_traversable = NULL;
+ starting_view = NULL;
+ v = focus_traversable->FindNextFocusableView(starting_view,
+ reverse,
+ FocusTraversable::DOWN,
+ dont_loop,
+ &new_focus_traversable,
+ &new_starting_view);
+ }
+ return v;
+}
+
+AcceleratorTarget* FocusManager::RegisterAccelerator(
+ const Accelerator& accelerator,
+ AcceleratorTarget* target) {
+ AcceleratorMap::const_iterator iter = accelerators_.find(accelerator);
+ AcceleratorTarget* previous_target = NULL;
+ if (iter != accelerators_.end())
+ previous_target = iter->second;
+
+ accelerators_[accelerator] = target;
+
+ return previous_target;
+}
+
+void FocusManager::UnregisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target) {
+ AcceleratorMap::iterator iter = accelerators_.find(accelerator);
+ if (iter == accelerators_.end()) {
+ NOTREACHED() << "Unregistering non-existing accelerator";
+ return;
+ }
+
+ if (iter->second != target) {
+ NOTREACHED() << "Unregistering accelerator for wrong target";
+ return;
+ }
+
+ accelerators_.erase(iter);
+}
+
+void FocusManager::UnregisterAccelerators(AcceleratorTarget* target) {
+ for (AcceleratorMap::iterator iter = accelerators_.begin();
+ iter != accelerators_.end();) {
+ if (iter->second == target)
+ accelerators_.erase(iter++);
+ else
+ ++iter;
+ }
+}
+
+bool FocusManager::ProcessAccelerator(const Accelerator& accelerator) {
+ FocusManager* focus_manager = this;
+ do {
+ AcceleratorTarget* target =
+ focus_manager->GetTargetForAccelerator(accelerator);
+ if (target) {
+ // If there is focused view, we give it a chance to process that
+ // accelerator.
+ if (!focused_view_ ||
+ !focused_view_->OverrideAccelerator(accelerator)) {
+ if (target->AcceleratorPressed(accelerator))
+ return true;
+ }
+ }
+
+ // When dealing with child windows that have their own FocusManager (such
+ // as ConstrainedWindow), we still want the parent FocusManager to process
+ // the accelerator if the child window did not process it.
+ focus_manager = focus_manager->GetParentFocusManager();
+ } while (focus_manager);
+ return false;
+}
+
+AcceleratorTarget* FocusManager::GetTargetForAccelerator(
+ const Accelerator& accelerator) const {
+ AcceleratorMap::const_iterator iter = accelerators_.find(accelerator);
+ if (iter != accelerators_.end())
+ return iter->second;
+ return NULL;
+}
+
+void FocusManager::ViewRemoved(View* parent, View* removed) {
+ if (focused_view_ && focused_view_ == removed)
+ focused_view_ = NULL;
+}
+
+void FocusManager::AddKeystrokeListener(KeystrokeListener* listener) {
+ DCHECK(std::find(keystroke_listeners_.begin(), keystroke_listeners_.end(),
+ listener) == keystroke_listeners_.end())
+ << "Adding a listener twice.";
+ keystroke_listeners_.push_back(listener);
+}
+
+void FocusManager::RemoveKeystrokeListener(KeystrokeListener* listener) {
+ KeystrokeListenerList::iterator place =
+ std::find(keystroke_listeners_.begin(), keystroke_listeners_.end(),
+ listener);
+ if (place == keystroke_listeners_.end()) {
+ NOTREACHED() << "Removing a listener that isn't registered.";
+ return;
+ }
+ keystroke_listeners_.erase(place);
+}
+
+void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) {
+ DCHECK(std::find(focus_change_listeners_.begin(),
+ focus_change_listeners_.end(), listener) ==
+ focus_change_listeners_.end()) << "Adding a listener twice.";
+ focus_change_listeners_.push_back(listener);
+}
+
+void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) {
+ FocusChangeListenerList::iterator place =
+ std::find(focus_change_listeners_.begin(), focus_change_listeners_.end(),
+ listener);
+ if (place == focus_change_listeners_.end()) {
+ NOTREACHED() << "Removing a listener that isn't registered.";
+ return;
+ }
+ focus_change_listeners_.erase(place);
+}
+
+} // namespace views
diff --git a/views/focus/focus_manager.h b/views/focus/focus_manager.h
new file mode 100644
index 0000000..8c33036
--- /dev/null
+++ b/views/focus/focus_manager.h
@@ -0,0 +1,343 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_FOCUS_FOCUS_MANAGER_H_
+#define VIEWS_FOCUS_FOCUS_MANAGER_H_
+
+#include "base/basictypes.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#include <vector>
+#include <map>
+
+#include "views/accelerator.h"
+
+// The FocusManager class is used to handle focus traversal, store/restore
+// focused views and handle keyboard accelerators.
+//
+// There are 2 types of focus:
+// - the native focus, which is the focus that an HWND has.
+// - the view focus, which is the focus that a views::View has.
+//
+// Each native view must register with their Focus Manager so the focus manager
+// gets notified when they are focused (and keeps track of the native focus) and
+// as well so that the tab key events can be intercepted.
+// They can provide when they register a View that is kept in synch in term of
+// focus. This is used in NativeControl for example, where a View wraps an
+// actual native window.
+// This is already done for you if you subclass the NativeControl class or if
+// you use the HWNDView class.
+//
+// When creating a top window, if it derives from WidgetWin, the
+// |has_own_focus_manager| of the Init method lets you specify whether that
+// window should have its own focus manager (so focus traversal stays confined
+// in that window). If you are not deriving from WidgetWin or one of its
+// derived classes (Window, FramelessWindow, ConstrainedWindow), you must
+// create a FocusManager when the window is created (it is automatically deleted
+// when the window is destroyed).
+//
+// The FocusTraversable interface exposes the methods a class should implement
+// in order to be able to be focus traversed when tab key is pressed.
+// RootViews implement FocusTraversable.
+// The FocusManager contains a top FocusTraversable instance, which is the top
+// RootView.
+//
+// If you just use views, then the focus traversal is handled for you by the
+// RootView. The default traversal order is the order in which the views have
+// been added to their container. You can modify this order by using the View
+// method SetNextFocusableView().
+//
+// If you are embedding a native view containing a nested RootView (for example
+// by adding a NativeControl that contains a WidgetWin as its native
+// component), then you need to:
+// - override the View::GetFocusTraversable() method in your outter component.
+// It should return the RootView of the inner component. This is used when
+// the focus traversal traverse down the focus hierarchy to enter the nested
+// RootView. In the example mentioned above, the NativeControl overrides
+// GetFocusTraversable() and returns hwnd_view_container_->GetRootView().
+// - call RootView::SetFocusTraversableParent() on the nested RootView and point
+// it to the outter RootView. This is used when the focus goes out of the
+// nested RootView. In the example:
+// hwnd_view_container_->GetRootView()->SetFocusTraversableParent(
+// native_control->GetRootView());
+// - call RootView::SetFocusTraversableParentView() on the nested RootView with
+// the parent view that directly contains the native window. This is needed
+// when traversing up from the nested RootView to know which view to start
+// with when going to the next/previous view.
+// In our example:
+// hwnd_view_container_->GetRootView()->SetFocusTraversableParent(
+// native_control);
+//
+// Note that FocusTraversable do not have to be RootViews: TabContents is
+// FocusTraversable.
+
+namespace views {
+
+class View;
+class RootView;
+
+// The FocusTraversable interface is used by components that want to process
+// focus traversal events (due to Tab/Shift-Tab key events).
+class FocusTraversable {
+ public:
+ // The direction in which the focus traversal is going.
+ // TODO (jcampan): add support for lateral (left, right) focus traversal. The
+ // goal is to switch to focusable views on the same level when using the arrow
+ // keys (ala Windows: in a dialog box, arrow keys typically move between the
+ // dialog OK, Cancel buttons).
+ enum Direction {
+ UP = 0,
+ DOWN
+ };
+
+ // Should find the next view that should be focused and return it. If a
+ // FocusTraversable is found while searching for the focusable view, NULL
+ // should be returned, focus_traversable should be set to the FocusTraversable
+ // and focus_traversable_view should be set to the view associated with the
+ // FocusTraversable.
+ // This call should return NULL if the end of the focus loop is reached.
+ // - |starting_view| is the view that should be used as the starting point
+ // when looking for the previous/next view. It may be NULL (in which case
+ // the first/last view should be used depending if normal/reverse).
+ // - |reverse| whether we should find the next (reverse is false) or the
+ // previous (reverse is true) view.
+ // - |direction| specifies whether we are traversing down (meaning we should
+ // look into child views) or traversing up (don't look at child views).
+ // - |dont_loop| if true specifies that if there is a loop in the focus
+ // hierarchy, we should keep traversing after the last view of the loop.
+ // - |focus_traversable| is set to the focus traversable that should be
+ // traversed if one is found (in which case the call returns NULL).
+ // - |focus_traversable_view| is set to the view associated with the
+ // FocusTraversable set in the previous parameter (it is used as the
+ // starting view when looking for the next focusable view).
+
+ virtual View* FindNextFocusableView(View* starting_view,
+ bool reverse,
+ Direction direction,
+ bool dont_loop,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view) = 0;
+
+ // Should return the parent FocusTraversable.
+ // The top RootView which is the top FocusTraversable returns NULL.
+ virtual FocusTraversable* GetFocusTraversableParent() = 0;
+
+ // This should return the View this FocusTraversable belongs to.
+ // It is used when walking up the view hierarchy tree to find which view
+ // should be used as the starting view for finding the next/previous view.
+ virtual View* GetFocusTraversableParentView() = 0;
+};
+
+// The KeystrokeListener interface is used by components (such as the
+// ExternalTabContainer class) which need a crack at handling all
+// keystrokes.
+class KeystrokeListener {
+ public:
+ // If this returns true, then the component handled the keystroke and ate
+ // it.
+#if defined(OS_WIN)
+ virtual bool ProcessKeyStroke(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) = 0;
+#endif
+};
+
+// This interface should be implemented by classes that want to be notified when
+// the focus is about to change. See the Add/RemoveFocusChangeListener methods.
+class FocusChangeListener {
+ public:
+ virtual void FocusWillChange(View* focused_before, View* focused_now) = 0;
+};
+
+class FocusManager {
+ public:
+#if defined(OS_WIN)
+ // Creates a FocusManager for the specified window. Top level windows
+ // must invoked this when created.
+ // The RootView specified should be the top RootView of the window.
+ // This also invokes InstallFocusSubclass.
+ static FocusManager* CreateFocusManager(HWND window, RootView* root_view);
+
+ // Subclasses the specified window. The subclassed window procedure listens
+ // for WM_SETFOCUS notification and keeps the FocusManager's focus owner
+ // property in sync.
+ // It's not necessary to explicitly invoke Uninstall, it's automatically done
+ // when the window is destroyed and Uninstall wasn't invoked.
+ static void InstallFocusSubclass(HWND window, View* view);
+
+ // Uninstalls the window subclass installed by InstallFocusSubclass.
+ static void UninstallFocusSubclass(HWND window);
+
+ static FocusManager* GetFocusManager(HWND window);
+
+ // Message handlers (for messages received from registered windows).
+ // Should return true if the message should be forwarded to the window
+ // original proc function, false otherwise.
+ bool OnSetFocus(HWND window);
+ bool OnNCDestroy(HWND window);
+ // OnKeyDown covers WM_KEYDOWN and WM_SYSKEYDOWN.
+ bool OnKeyDown(HWND window,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam);
+ bool OnKeyUp(HWND window,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam);
+ // OnPostActivate is called after WM_ACTIVATE has been propagated to the
+ // DefWindowProc.
+ bool OnPostActivate(HWND window, int activation_state, int minimized_state);
+#endif
+
+ // Returns true is the specified is part of the hierarchy of the window
+ // associated with this FocusManager.
+ bool ContainsView(View* view);
+
+ // Advances the focus (backward if reverse is true).
+ void AdvanceFocus(bool reverse);
+
+ // The FocusManager is handling the selected view for the RootView.
+ View* GetFocusedView() const { return focused_view_; }
+ void SetFocusedView(View* view);
+
+ // Clears the focused view. The window associated with the top root view gets
+ // the native focus (so we still get keyboard events).
+ void ClearFocus();
+
+ // Clears the HWND that has the focus by focusing the HWND from the top
+ // RootView (so we still get keyboard events).
+ // Note that this does not change the currently focused view.
+ void ClearHWNDFocus();
+
+#if defined(OS_WIN)
+ // Focus the specified |hwnd| without changing the focused view.
+ void FocusHWND(HWND hwnd);
+#endif
+
+ // Validates the focused view, clearing it if the window it belongs too is not
+ // attached to the window hierarchy anymore.
+ void ValidateFocusedView();
+
+#if defined(OS_WIN)
+ // Returns the view associated with the specified window if any.
+ // If |look_in_parents| is true, it goes up the window parents until it find
+ // a view.
+ static View* GetViewForWindow(HWND window, bool look_in_parents);
+#endif
+
+ // Stores and restores the focused view. Used when the window becomes
+ // active/inactive.
+ void StoreFocusedView();
+ void RestoreFocusedView();
+
+ // Clears the stored focused view.
+ void ClearStoredFocusedView();
+
+ // Returns the FocusManager of the parent window of the window that is the
+ // root of this FocusManager. This is useful with ConstrainedWindows that have
+ // their own FocusManager and need to return focus to the browser when closed.
+ FocusManager* GetParentFocusManager() const;
+
+ // Register a keyboard accelerator for the specified target. If an
+ // AcceleratorTarget is already registered for that accelerator, it is
+ // returned.
+ // Note that we are currently limited to accelerators that are either:
+ // - a key combination including Ctrl or Alt
+ // - the escape key
+ // - the enter key
+ // - any F key (F1, F2, F3 ...)
+ // - any browser specific keys (as available on special keyboards)
+ AcceleratorTarget* RegisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target);
+
+ // Unregister the specified keyboard accelerator for the specified target.
+ void UnregisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target);
+
+ // Unregister all keyboard accelerator for the specified target.
+ void UnregisterAccelerators(AcceleratorTarget* target);
+
+ // Activate the target associated with the specified accelerator if any.
+ // Returns true if an accelerator was activated.
+ bool ProcessAccelerator(const Accelerator& accelerator);
+
+ // Called by a RootView when a view within its hierarchy is removed from its
+ // parent. This will only be called by a RootView in a hierarchy of Widgets
+ // that this FocusManager is attached to the parent Widget of.
+ void ViewRemoved(View* parent, View* removed);
+
+ void AddKeystrokeListener(KeystrokeListener* listener);
+ void RemoveKeystrokeListener(KeystrokeListener* listener);
+
+ // Adds/removes a listener. The FocusChangeListener is notified every time
+ // the focused view is about to change.
+ void AddFocusChangeListener(FocusChangeListener* listener);
+ void RemoveFocusChangeListener(FocusChangeListener* listener);
+
+ // Returns the AcceleratorTarget that should be activated for the specified
+ // keyboard accelerator, or NULL if no view is registered for that keyboard
+ // accelerator.
+ // TODO(finnur): http://b/1307173 Make this private once the bug is fixed.
+ AcceleratorTarget* GetTargetForAccelerator(
+ const Accelerator& accelerator) const;
+
+ private:
+#if defined(OS_WIN)
+ explicit FocusManager(HWND root, RootView* root_view);
+#endif
+ ~FocusManager();
+
+ // Returns the next focusable view.
+ View* GetNextFocusableView(View* starting_view, bool reverse, bool dont_loop);
+
+ // Returns the last view of the focus traversal hierarchy.
+ View* FindLastFocusableView();
+
+ // Returns the focusable view found in the FocusTraversable specified starting
+ // at the specified view. This traverses down along the FocusTraversable
+ // hierarchy.
+ // Returns NULL if no focusable view were found.
+ View* FindFocusableView(FocusTraversable* focus_traversable,
+ View* starting_view,
+ bool reverse,
+ bool dont_loop);
+
+ // The RootView of the window associated with this FocusManager.
+ RootView* top_root_view_;
+
+ // The view that currently is focused.
+ View* focused_view_;
+
+ // The storage id used in the ViewStorage to store/restore the view that last
+ // had focus.
+ int stored_focused_view_storage_id_;
+
+#if defined(OS_WIN)
+ // The window associated with this focus manager.
+ HWND root_;
+#endif
+
+ // Used to allow setting the focus on an HWND without changing the currently
+ // focused view.
+ bool ignore_set_focus_msg_;
+
+ // The accelerators and associated targets.
+ typedef std::map<Accelerator, AcceleratorTarget*> AcceleratorMap;
+ AcceleratorMap accelerators_;
+
+ // The list of registered keystroke listeners
+ typedef std::vector<KeystrokeListener*> KeystrokeListenerList;
+ KeystrokeListenerList keystroke_listeners_;
+
+ // The list of registered FocusChange listeners.
+ typedef std::vector<FocusChangeListener*> FocusChangeListenerList;
+ FocusChangeListenerList focus_change_listeners_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusManager);
+};
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_FOCUS_MANAGER_H_
diff --git a/views/focus/focus_manager_unittest.cc b/views/focus/focus_manager_unittest.cc
new file mode 100644
index 0000000..234db1d
--- /dev/null
+++ b/views/focus/focus_manager_unittest.cc
@@ -0,0 +1,666 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Disabled right now as this won't work on BuildBots right now as this test
+// require the box it runs on to be unlocked (and no screen-savers).
+// The test actually simulates mouse and key events, so if the screen is locked,
+// the events don't go to the Chrome window.
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "app/resource_bundle.h"
+#include "base/gfx/rect.h"
+#include "skia/include/SkColor.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/button/checkbox.h"
+#include "views/controls/button/native_button.h"
+#include "views/controls/button/radio_button.h"
+#include "views/controls/label.h"
+#include "views/controls/link.h"
+#include "views/controls/scroll_view.h"
+#include "views/controls/tabbed_pane.h"
+#include "views/controls/text_field.h"
+#include "views/widget/accelerator_handler.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+#include "views/window/window.h"
+#include "views/window/window_delegate.h"
+
+
+
+namespace {
+
+static const int kWindowWidth = 600;
+static const int kWindowHeight = 500;
+
+static int count = 1;
+
+static const int kTopCheckBoxID = count++; // 1
+static const int kLeftContainerID = count++;
+static const int kAppleLabelID = count++;
+static const int kAppleTextFieldID = count++;
+static const int kOrangeLabelID = count++; // 5
+static const int kOrangeTextFieldID = count++;
+static const int kBananaLabelID = count++;
+static const int kBananaTextFieldID = count++;
+static const int kKiwiLabelID = count++;
+static const int kKiwiTextFieldID = count++; // 10
+static const int kFruitButtonID = count++;
+static const int kFruitCheckBoxID = count++;
+
+static const int kRightContainerID = count++;
+static const int kAsparagusButtonID = count++;
+static const int kBroccoliButtonID = count++; // 15
+static const int kCauliflowerButtonID = count++;
+
+static const int kInnerContainerID = count++;
+static const int kScrollViewID = count++;
+static const int kScrollContentViewID = count++;
+static const int kRosettaLinkID = count++; // 20
+static const int kStupeurEtTremblementLinkID = count++;
+static const int kDinerGameLinkID = count++;
+static const int kRidiculeLinkID = count++;
+static const int kClosetLinkID = count++;
+static const int kVisitingLinkID = count++; // 25
+static const int kAmelieLinkID = count++;
+static const int kJoyeuxNoelLinkID = count++;
+static const int kCampingLinkID = count++;
+static const int kBriceDeNiceLinkID = count++;
+static const int kTaxiLinkID = count++; // 30
+static const int kAsterixLinkID = count++;
+
+static const int kOKButtonID = count++;
+static const int kCancelButtonID = count++;
+static const int kHelpButtonID = count++;
+
+static const int kStyleContainerID = count++; // 35
+static const int kBoldCheckBoxID = count++;
+static const int kItalicCheckBoxID = count++;
+static const int kUnderlinedCheckBoxID = count++;
+
+static const int kSearchContainerID = count++;
+static const int kSearchTextFieldID = count++; // 40
+static const int kSearchButtonID = count++;
+static const int kHelpLinkID = count++;
+
+static const int kThumbnailContainerID = count++;
+static const int kThumbnailStarID = count++;
+static const int kThumbnailSuperStarID = count++;
+
+class FocusManagerTest;
+
+// BorderView is a NativeControl that creates a tab control as its child and
+// takes a View to add as the child of the tab control. The tab control is used
+// to give a nice background for the view. At some point we'll have a real
+// wrapper for TabControl, and this can be nuked in favor of it.
+// Taken from keyword_editor_view.cc.
+// It is interesting in our test as it is a native control containing another
+// RootView.
+class BorderView : public views::NativeControl {
+ public:
+ explicit BorderView(View* child) : child_(child) {
+ DCHECK(child);
+ SetFocusable(false);
+ }
+
+ virtual ~BorderView() {}
+
+ virtual HWND CreateNativeControl(HWND parent_container) {
+ // Create the tab control.
+ HWND tab_control = ::CreateWindowEx(GetAdditionalExStyle(),
+ WC_TABCONTROL,
+ L"",
+ WS_CHILD,
+ 0, 0, width(), height(),
+ parent_container, NULL, NULL, NULL);
+ // Create the view container which is a child of the TabControl.
+ widget_ = new views::WidgetWin();
+ widget_->Init(tab_control, gfx::Rect(), false);
+ widget_->SetContentsView(child_);
+ widget_->SetFocusTraversableParentView(this);
+ ResizeContents(tab_control);
+ return tab_control;
+ }
+
+ virtual LRESULT OnNotify(int w_param, LPNMHDR l_param) {
+ return 0;
+ }
+
+ virtual void Layout() {
+ NativeControl::Layout();
+ ResizeContents(GetNativeControlHWND());
+ }
+
+ virtual views::RootView* GetContentsRootView() {
+ return widget_->GetRootView();
+ }
+
+ virtual views::FocusTraversable* GetFocusTraversable() {
+ return widget_;
+ }
+
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child) {
+ NativeControl::ViewHierarchyChanged(is_add, parent, child);
+
+ if (child == this && is_add) {
+ // We have been added to a view hierarchy, update the FocusTraversable
+ // parent.
+ widget_->SetFocusTraversableParent(GetRootView());
+ }
+ }
+
+private:
+ void ResizeContents(HWND tab_control) {
+ DCHECK(tab_control);
+ CRect content_bounds;
+ if (!GetClientRect(tab_control, &content_bounds))
+ return;
+ TabCtrl_AdjustRect(tab_control, FALSE, &content_bounds);
+ widget_->MoveWindow(content_bounds.left, content_bounds.top,
+ content_bounds.Width(), content_bounds.Height(),
+ TRUE);
+ }
+
+ View* child_;
+ views::WidgetWin* widget_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BorderView);
+};
+
+class TestViewWindow : public views::WidgetWin {
+ public:
+ explicit TestViewWindow(FocusManagerTest* test);
+ ~TestViewWindow() { }
+
+ void Init();
+
+ views::View* contents() const { return contents_; }
+
+
+ // Return the ID of the component that currently has the focus.
+ int GetFocusedComponentID();
+
+ // Simulate pressing the tab button in the window.
+ void PressTab(bool shift_pressed, bool ctrl_pressed);
+
+ views::RootView* GetContentsRootView() const {
+ return contents_->GetRootView();
+ }
+
+ views::RootView* GetStyleRootView() const {
+ return style_tab_->GetContentsRootView();
+ }
+
+ views::RootView* GetSearchRootView() const {
+ return search_border_view_->GetContentsRootView();
+ }
+
+ private:
+ views::View* contents_;
+
+ views::TabbedPane* style_tab_;
+ BorderView* search_border_view_;
+
+ FocusManagerTest* test_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestViewWindow);
+};
+
+class FocusManagerTest : public testing::Test {
+ public:
+ TestViewWindow* GetWindow();
+ ~FocusManagerTest();
+
+ protected:
+ FocusManagerTest();
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ MessageLoopForUI message_loop_;
+ TestViewWindow* test_window_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// TestViewWindow
+////////////////////////////////////////////////////////////////////////////////
+
+TestViewWindow::TestViewWindow(FocusManagerTest* test)
+ : test_(test),
+ contents_(NULL),
+ style_tab_(NULL),
+ search_border_view_(NULL) {
+}
+
+// Initializes and shows the window with the contents view.
+void TestViewWindow::Init() {
+ gfx::Rect bounds(0, 0, 600, 460);
+ contents_ = new views::View();
+ contents_->set_background(
+ views::Background::CreateSolidBackground(255, 255, 255));
+
+ WidgetWin::Init(NULL, bounds, true);
+ SetContentsView(contents_);
+
+ views::Checkbox* cb =
+ new views::Checkbox(L"This is a checkbox");
+ contents_->AddChildView(cb);
+ // In this fast paced world, who really has time for non hard-coded layout?
+ cb->SetBounds(10, 10, 200, 20);
+ cb->SetID(kTopCheckBoxID);
+
+ views::View* left_container = new views::View();
+ left_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ left_container->set_background(
+ views::Background::CreateSolidBackground(240, 240, 240));
+ left_container->SetID(kLeftContainerID);
+ contents_->AddChildView(left_container);
+ left_container->SetBounds(10, 35, 250, 200);
+
+ int label_x = 5;
+ int label_width = 50;
+ int label_height = 15;
+ int text_field_width = 150;
+ int y = 10;
+ int gap_between_labels = 10;
+
+ views::Label* label = new views::Label(L"Apple:");
+ label->SetID(kAppleLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ views::TextField* text_field = new views::TextField();
+ text_field->SetID(kAppleTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Orange:");
+ label->SetID(kOrangeLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kOrangeTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Banana:");
+ label->SetID(kBananaLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kBananaTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Kiwi:");
+ label->SetID(kKiwiLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kKiwiTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ views::NativeButton* button = new views::NativeButton(NULL, L"Click me");
+ button->SetBounds(label_x, y + 10, 50, 20);
+ button->SetID(kFruitButtonID);
+ left_container->AddChildView(button);
+ y += 40;
+
+ cb = new views::Checkbox(L"This is another check box");
+ cb->SetBounds(label_x + label_width + 5, y, 100, 20);
+ cb->SetID(kFruitCheckBoxID);
+ left_container->AddChildView(cb);
+
+ views::View* right_container = new views::View();
+ right_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ right_container->set_background(
+ views::Background::CreateSolidBackground(240, 240, 240));
+ right_container->SetID(kRightContainerID);
+ contents_->AddChildView(right_container);
+ right_container->SetBounds(270, 35, 300, 200);
+
+ y = 10;
+ int radio_button_height = 15;
+ int gap_between_radio_buttons = 10;
+ views::View* radio_button =
+ new views::RadioButton(L"Asparagus", 1);
+ radio_button->SetID(kAsparagusButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new views::RadioButton(L"Broccoli", 1);
+ radio_button->SetID(kBroccoliButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new views::RadioButton(L"Cauliflower", 1);
+ radio_button->SetID(kCauliflowerButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+
+ views::View* inner_container = new views::View();
+ inner_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ inner_container->set_background(
+ views::Background::CreateSolidBackground(230, 230, 230));
+ inner_container->SetID(kInnerContainerID);
+ right_container->AddChildView(inner_container);
+ inner_container->SetBounds(100, 10, 150, 180);
+
+ views::ScrollView* scroll_view = new views::ScrollView();
+ scroll_view->SetID(kScrollViewID);
+ inner_container->AddChildView(scroll_view);
+ scroll_view->SetBounds(1, 1, 148, 178);
+
+ views::View* scroll_content = new views::View();
+ scroll_content->SetBounds(0, 0, 200, 200);
+ scroll_content->set_background(
+ views::Background::CreateSolidBackground(200, 200, 200));
+ scroll_view->SetContents(scroll_content);
+
+ static const wchar_t* const kTitles[] = {
+ L"Rosetta", L"Stupeur et tremblement", L"The diner game",
+ L"Ridicule", L"Le placard", L"Les Visiteurs", L"Amelie",
+ L"Joyeux Noel", L"Camping", L"Brice de Nice",
+ L"Taxi", L"Asterix"
+ };
+
+ static const int kIDs[] = {
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
+ kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID
+ };
+
+ DCHECK(arraysize(kTitles) == arraysize(kIDs));
+
+ y = 5;
+ for (int i = 0; i < arraysize(kTitles); ++i) {
+ views::Link* link = new views::Link(kTitles[i]);
+ link->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ link->SetID(kIDs[i]);
+ scroll_content->AddChildView(link);
+ link->SetBounds(5, y, 300, 15);
+ y += 15;
+ }
+
+ y = 250;
+ int width = 50;
+ button = new views::NativeButton(NULL, L"OK");
+ button->SetID(kOKButtonID);
+
+ contents_->AddChildView(button);
+ button->SetBounds(150, y, width, 20);
+
+ button = new views::NativeButton(NULL, L"Cancel");
+ button->SetID(kCancelButtonID);
+ contents_->AddChildView(button);
+ button->SetBounds(250, y, width, 20);
+
+ button = new views::NativeButton(NULL, L"Help");
+ button->SetID(kHelpButtonID);
+ contents_->AddChildView(button);
+ button->SetBounds(350, y, width, 20);
+
+ y += 40;
+
+ // Left bottom box with style checkboxes.
+ views::View* contents = new views::View();
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorWHITE));
+ cb = new views::Checkbox(L"Bold");
+ contents->AddChildView(cb);
+ cb->SetBounds(10, 10, 50, 20);
+ cb->SetID(kBoldCheckBoxID);
+
+ cb = new views::Checkbox(L"Italic");
+ contents->AddChildView(cb);
+ cb->SetBounds(70, 10, 50, 20);
+ cb->SetID(kItalicCheckBoxID);
+
+ cb = new views::Checkbox(L"Underlined");
+ contents->AddChildView(cb);
+ cb->SetBounds(130, 10, 70, 20);
+ cb->SetID(kUnderlinedCheckBoxID);
+
+ style_tab_ = new views::TabbedPane();
+ style_tab_->SetID(kStyleContainerID);
+ contents_->AddChildView(style_tab_);
+ style_tab_->SetBounds(10, y, 210, 50);
+ style_tab_->AddTab(L"Style", contents);
+ style_tab_->AddTab(L"Other", new views::View());
+
+ // Right bottom box with search.
+ contents = new views::View();
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorWHITE));
+ text_field = new views::TextField();
+ contents->AddChildView(text_field);
+ text_field->SetBounds(10, 10, 100, 20);
+ text_field->SetID(kSearchTextFieldID);
+
+ button = new views::NativeButton(NULL, L"Search");
+ contents->AddChildView(button);
+ button->SetBounds(115, 10, 50, 20);
+ button->SetID(kSearchButtonID);
+
+ views::Link* link = new views::Link(L"Help");
+ link->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ link->SetID(kHelpLinkID);
+ contents->AddChildView(link);
+ link->SetBounds(170, 10, 30, 15);
+
+ search_border_view_ = new BorderView(contents);
+ search_border_view_->SetID(kSearchContainerID);
+
+ contents_->AddChildView(search_border_view_);
+ search_border_view_->SetBounds(300, y, 200, 50);
+
+ y += 60;
+
+ contents = new views::View();
+ contents->SetFocusable(true);
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorBLUE));
+ contents->SetID(kThumbnailContainerID);
+ button = new views::NativeButton(NULL, L"Star");
+ contents->AddChildView(button);
+ button->SetBounds(5, 5, 50, 20);
+ button->SetID(kThumbnailStarID);
+ button = new views::NativeButton(NULL, L"SuperStar");
+ contents->AddChildView(button);
+ button->SetBounds(60, 5, 100, 20);
+ button->SetID(kThumbnailSuperStarID);
+
+ contents_->AddChildView(contents);
+ contents->SetBounds(200, y, 200, 50);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusManagerTest
+////////////////////////////////////////////////////////////////////////////////
+
+FocusManagerTest::FocusManagerTest() {
+}
+
+FocusManagerTest::~FocusManagerTest() {
+}
+
+TestViewWindow* FocusManagerTest::GetWindow() {
+ return test_window_;
+}
+
+void FocusManagerTest::SetUp() {
+ OleInitialize(NULL);
+ test_window_ = new TestViewWindow(this);
+ test_window_->Init();
+ ShowWindow(test_window_->GetNativeView(), SW_SHOW);
+}
+
+void FocusManagerTest::TearDown() {
+ test_window_->CloseNow();
+
+ // Flush the message loop to make Purify happy.
+ message_loop_.RunAllPending();
+ OleUninitialize();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The tests
+////////////////////////////////////////////////////////////////////////////////
+
+
+TEST_F(FocusManagerTest, NormalTraversal) {
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextFieldID,
+ kOrangeTextFieldID, kBananaTextFieldID, kKiwiTextFieldID,
+ kFruitButtonID, kFruitCheckBoxID, kAsparagusButtonID, kRosettaLinkID,
+ kStupeurEtTremblementLinkID,
+ kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
+ kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
+ kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
+ kUnderlinedCheckBoxID, kSearchTextFieldID, kSearchButtonID, kHelpLinkID,
+ kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
+
+ // Uncomment the following line if you want to test manually the UI of this
+ // test.
+ // MessageLoop::current()->Run(new views::AcceleratorHandler());
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(test_window_->GetNativeView());
+ // Let's traverse the whole focus hierarchy (several times, to make sure it
+ // loops OK).
+ focus_manager->SetFocusedView(NULL);
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < arraysize(kTraversalIDs); j++) {
+ focus_manager->AdvanceFocus(false);
+ views::View* focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+
+ // Focus the 1st item.
+ views::RootView* root_view = test_window_->GetContentsRootView();
+ focus_manager->SetFocusedView(root_view->GetViewByID(kTraversalIDs[0]));
+
+ /* BROKEN because of bug #1153276. The reverse order of traversal in Tabbed
+ Panes is broken (we go to the tab before going to the content
+ // Let's traverse in reverse order.
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ focus_manager->AdvanceFocus(true);
+ views::View* focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+ */
+}
+
+TEST_F(FocusManagerTest, TraversalWithNonEnabledViews) {
+ const int kMainContentsDisabledIDs[] = {
+ kBananaTextFieldID, kFruitCheckBoxID, kAsparagusButtonID,
+ kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kHelpButtonID };
+
+ const int kStyleContentsDisabledIDs[] = { kBoldCheckBoxID };
+
+ const int kSearchContentsDisabledIDs[] = { kSearchTextFieldID, kHelpLinkID };
+
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextFieldID,
+ kOrangeTextFieldID, kKiwiTextFieldID, kFruitButtonID, kBroccoliButtonID,
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
+ kOKButtonID, kCancelButtonID, kStyleContainerID,
+ kItalicCheckBoxID, kUnderlinedCheckBoxID, kSearchButtonID,
+ kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
+
+ // Let's disable some views.
+ views::RootView* root_view = test_window_->GetContentsRootView();
+ for (int i = 0; i < arraysize(kMainContentsDisabledIDs); i++) {
+ views::View* v = root_view->GetViewByID(kMainContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+ root_view = test_window_->GetStyleRootView();
+ for (int i = 0; i < arraysize(kStyleContentsDisabledIDs); i++) {
+ views::View* v = root_view->GetViewByID(kStyleContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+ root_view = test_window_->GetSearchRootView();
+ for (int i = 0; i < arraysize(kSearchContentsDisabledIDs); i++) {
+ views::View* v =
+ root_view->GetViewByID(kSearchContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(test_window_->GetNativeView());
+ views::View* focused_view;
+ // Let's do one traversal (several times, to make sure it loops ok).
+ for (int i = 0; i < 3;++i) {
+ for (int j = 0; j < arraysize(kTraversalIDs); j++) {
+ focus_manager->AdvanceFocus(false);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+
+ // Focus the 1st item.
+ focus_manager->AdvanceFocus(false);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[0], focused_view->GetID());
+
+ // Same thing in reverse.
+ /* BROKEN because of bug #1153276. The reverse order of traversal in Tabbed
+ Panes is broken (we go to the tab before going to the content
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ focus_manager->AdvanceFocus(true);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+ */
+}
+
+}
diff --git a/views/focus/focus_util_win.cc b/views/focus/focus_util_win.cc
new file mode 100644
index 0000000..046df70
--- /dev/null
+++ b/views/focus/focus_util_win.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/focus/focus_util_win.h"
+
+#include <windowsx.h>
+
+#include "base/win_util.h"
+
+namespace views {
+
+// Property used to indicate the HWND supports having mouse wheel messages
+// rerouted to it.
+static const wchar_t* const kHWNDSupportMouseWheelRerouting =
+ L"__HWND_MW_REROUTE_OK";
+
+static bool WindowSupportsRerouteMouseWheel(HWND window) {
+ while (GetWindowLong(window, GWL_STYLE) & WS_CHILD) {
+ if (!IsWindow(window))
+ break;
+
+ if (reinterpret_cast<bool>(GetProp(window,
+ kHWNDSupportMouseWheelRerouting))) {
+ return true;
+ }
+ window = GetParent(window);
+ }
+ return false;
+}
+
+static bool IsCompatibleWithMouseWheelRedirection(HWND window) {
+ std::wstring class_name = win_util::GetClassName(window);
+ // Mousewheel redirection to comboboxes is a surprising and
+ // undesireable user behavior.
+ return !(class_name == L"ComboBox" ||
+ class_name == L"ComboBoxEx32");
+}
+
+static bool CanRedirectMouseWheelFrom(HWND window) {
+ std::wstring class_name = win_util::GetClassName(window);
+
+ // Older Thinkpad mouse wheel drivers create a window under mouse wheel
+ // pointer. Detect if we are dealing with this window. In this case we
+ // don't need to do anything as the Thinkpad mouse driver will send
+ // mouse wheel messages to the right window.
+ if ((class_name == L"Syn Visual Class") ||
+ (class_name == L"SynTrackCursorWindowClass"))
+ return false;
+
+ return true;
+}
+
+void SetWindowSupportsRerouteMouseWheel(HWND hwnd) {
+ SetProp(hwnd, kHWNDSupportMouseWheelRerouting,
+ reinterpret_cast<HANDLE>(true));
+}
+
+bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param) {
+ // Since this is called from a subclass for every window, we can get
+ // here recursively. This will happen if, for example, a control
+ // reflects wheel scroll messages to its parent. Bail out if we got
+ // here recursively.
+ static bool recursion_break = false;
+ if (recursion_break)
+ return false;
+ // Check if this window's class has a bad interaction with rerouting.
+ if (!IsCompatibleWithMouseWheelRedirection(window))
+ return false;
+
+ DWORD current_process = GetCurrentProcessId();
+ POINT wheel_location = { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) };
+ HWND window_under_wheel = WindowFromPoint(wheel_location);
+
+ if (!CanRedirectMouseWheelFrom(window_under_wheel))
+ return false;
+
+ // Find the lowest Chrome window in the hierarchy that can be the
+ // target of mouse wheel redirection.
+ while (window != window_under_wheel) {
+ // If window_under_wheel is not a valid Chrome window, then return true to
+ // suppress further processing of the message.
+ if (!::IsWindow(window_under_wheel))
+ return true;
+ DWORD wheel_window_process = 0;
+ GetWindowThreadProcessId(window_under_wheel, &wheel_window_process);
+ if (current_process != wheel_window_process) {
+ if (IsChild(window, window_under_wheel)) {
+ // If this message is reflected from a child window in a different
+ // process (happens with out of process windowed plugins) then
+ // we don't want to reroute the wheel message.
+ return false;
+ } else {
+ // The wheel is scrolling over an unrelated window. Make sure that we
+ // have marked that window as supporting mouse wheel rerouting.
+ // Otherwise, we cannot send random WM_MOUSEWHEEL messages to arbitrary
+ // windows. So just drop the message.
+ if (!WindowSupportsRerouteMouseWheel(window_under_wheel))
+ return true;
+ }
+ }
+
+ // window_under_wheel is a Chrome window. If allowed, redirect.
+ if (IsCompatibleWithMouseWheelRedirection(window_under_wheel)) {
+ recursion_break = true;
+ SendMessage(window_under_wheel, WM_MOUSEWHEEL, w_param, l_param);
+ recursion_break = false;
+ return true;
+ }
+ // If redirection is disallowed, try the parent.
+ window_under_wheel = GetAncestor(window_under_wheel, GA_PARENT);
+ }
+ // If we traversed back to the starting point, we should process
+ // this message normally; return false.
+ return false;
+}
+
+} // namespace views
diff --git a/views/focus/focus_util_win.h b/views/focus/focus_util_win.h
new file mode 100644
index 0000000..832a6df
--- /dev/null
+++ b/views/focus/focus_util_win.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
+#define VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
+
+#include <windows.h>
+
+namespace views {
+
+// Marks the passed |hwnd| as supporting mouse-wheel message rerouting.
+// We reroute the mouse wheel messages to such HWND when they are under the
+// mouse pointer (but are not the active window)
+void SetWindowSupportsRerouteMouseWheel(HWND hwnd);
+
+// Forwards mouse wheel messages to the window under it.
+// Windows sends mouse wheel messages to the currently active window.
+// This causes a window to scroll even if it is not currently under the mouse
+// wheel. The following code gives mouse wheel messages to the window under the
+// mouse wheel in order to scroll that window. This is arguably a better user
+// experience. The returns value says whether the mouse wheel message was
+// successfully redirected.
+bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param);
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
diff --git a/views/focus/view_storage.cc b/views/focus/view_storage.cc
new file mode 100644
index 0000000..a108530
--- /dev/null
+++ b/views/focus/view_storage.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/focus/view_storage.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+
+namespace views {
+
+// This struct contains the information to locate a specific view.
+// Locating a view is not always straight-forward as a view can be a floating
+// view, or the child of a floating view. Floating views are frequently deleted
+// and recreated (typically when their container is laid out).
+struct ViewLocationInfo {
+ // True if the view is floating or the child of a floating view. False
+ // otherwise.
+ bool is_floating_view;
+
+ // If non floating, this is the stored view. If floating, the parent of the
+ // floating view.
+ View* view;
+
+ // The id of the floating view.
+ int floating_view_id;
+
+ // The path from the floating view to the stored view. The path is composed
+ // of the indexes of the views in the hierarchy.
+ std::vector<int> floating_view_to_view_path;
+};
+
+// static
+ViewStorage* ViewStorage::GetSharedInstance() {
+ return Singleton<ViewStorage>::get();
+}
+
+ViewStorage::ViewStorage() : view_storage_next_id_(0) {
+}
+
+ViewStorage::~ViewStorage() {
+ STLDeleteContainerPairSecondPointers(id_to_view_location_.begin(),
+ id_to_view_location_.end());
+
+ STLDeleteContainerPairSecondPointers(view_to_ids_.begin(),
+ view_to_ids_.end());
+}
+
+int ViewStorage::CreateStorageID() {
+ return view_storage_next_id_++;
+}
+
+void ViewStorage::StoreView(int storage_id, View* view) {
+ DCHECK(view);
+ std::map<int, ViewLocationInfo*>::iterator iter =
+ id_to_view_location_.find(storage_id);
+ DCHECK(iter == id_to_view_location_.end());
+
+ if (iter != id_to_view_location_.end())
+ RemoveView(storage_id);
+
+ ViewLocationInfo* view_location_info = new ViewLocationInfo();
+ View* floating_view_parent = view->RetrieveFloatingViewParent();
+ if (floating_view_parent) {
+ // The view is a floating view or is a child of a floating view.
+ view_location_info->is_floating_view = true;
+ view_location_info->view = floating_view_parent->GetParent();
+ view_location_info->floating_view_id =
+ floating_view_parent->GetFloatingViewID();
+ // Ley's store the path from the floating view to the actual view so we can
+ // locate it when restoring.
+ View::GetViewPath(floating_view_parent, view,
+ &(view_location_info->floating_view_to_view_path));
+ } else {
+ // It is a non floating view, it can be stored as is.
+ view_location_info->is_floating_view = false;
+ view_location_info->view = view;
+ }
+ id_to_view_location_[storage_id] = view_location_info;
+
+ std::vector<int>* ids = NULL;
+ std::map<View*, std::vector<int>*>::iterator id_iter =
+ view_to_ids_.find(view_location_info->view);
+ if (id_iter == view_to_ids_.end()) {
+ ids = new std::vector<int>();
+ view_to_ids_[view_location_info->view] = ids;
+ } else {
+ ids = id_iter->second;
+ }
+ ids->push_back(storage_id);
+}
+
+View* ViewStorage::RetrieveView(int storage_id) {
+ std::map<int, ViewLocationInfo*>::iterator iter =
+ id_to_view_location_.find(storage_id);
+ if (iter == id_to_view_location_.end())
+ return NULL;
+
+ ViewLocationInfo* view_location_info = iter->second;
+ if (view_location_info->is_floating_view) {
+ View* floating_view = view_location_info->view->
+ RetrieveFloatingViewForID(view_location_info->floating_view_id);
+ View* v = NULL;
+ if (floating_view) {
+ v = View::GetViewForPath(floating_view,
+ view_location_info->floating_view_to_view_path);
+ }
+ if (!v) {
+ // If we have not found the view, it means either the floating view with
+ // the id we have is gone, or it has changed and the actual child view
+ // we have the path for is not accessible. In that case, let's make sure
+ // we don't leak the ViewLocationInfo.
+ RemoveView(storage_id);
+ }
+ return v;
+ } else {
+ return view_location_info->view;
+ }
+}
+
+void ViewStorage::RemoveView(int storage_id) {
+ EraseView(storage_id, false);
+}
+
+void ViewStorage::ViewRemoved(View* parent, View* removed) {
+ // Let's first retrieve the ids for that view.
+ std::map<View*, std::vector<int>*>::iterator ids_iter =
+ view_to_ids_.find(removed);
+
+ if (ids_iter == view_to_ids_.end()) {
+ // That view is not in the view storage.
+ return;
+ }
+
+ std::vector<int>* ids = ids_iter->second;
+ DCHECK(!ids->empty());
+ EraseView((*ids)[0], true);
+}
+
+void ViewStorage::EraseView(int storage_id, bool remove_all_ids) {
+ // Remove the view from id_to_view_location_.
+ std::map<int, ViewLocationInfo*>::iterator location_iter =
+ id_to_view_location_.find(storage_id);
+ if (location_iter == id_to_view_location_.end())
+ return;
+
+ ViewLocationInfo* view_location = location_iter->second;
+ View* view = view_location->view;
+ delete view_location;
+ id_to_view_location_.erase(location_iter);
+
+ // Also update view_to_ids_.
+ std::map<View*, std::vector<int>*>::iterator ids_iter =
+ view_to_ids_.find(view);
+ DCHECK(ids_iter != view_to_ids_.end());
+ std::vector<int>* ids = ids_iter->second;
+
+ if (remove_all_ids) {
+ for (size_t i = 0; i < ids->size(); ++i) {
+ location_iter = id_to_view_location_.find((*ids)[i]);
+ if (location_iter != id_to_view_location_.end()) {
+ delete location_iter->second;
+ id_to_view_location_.erase(location_iter);
+ }
+ }
+ ids->clear();
+ } else {
+ std::vector<int>::iterator id_iter =
+ std::find(ids->begin(), ids->end(), storage_id);
+ DCHECK(id_iter != ids->end());
+ ids->erase(id_iter);
+ }
+
+ if (ids->empty()) {
+ delete ids;
+ view_to_ids_.erase(ids_iter);
+ }
+}
+
+} // namespace views
diff --git a/views/focus/view_storage.h b/views/focus/view_storage.h
new file mode 100644
index 0000000..9182429
--- /dev/null
+++ b/views/focus/view_storage.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_FOCUS_VIEW_STORAGE_H_
+#define VIEWS_FOCUS_VIEW_STORAGE_H_
+
+#include "base/singleton.h"
+#include "views/view.h"
+
+// This class is a simple storage place for storing/retrieving views. It is
+// used for example in the FocusManager to store/restore focused views when the
+// main window becomes active/inactive. It supports floating views, meaning
+// that when you store a view, it can be retrieved even if it is a floating
+// view or the child of a floating view that has been detached since the view
+// was stored (in which case the floating view is recreated and reattached).
+// It also automatically removes a view from the storage if the view is removed
+// from the tree hierarchy (or in the case of a floating view, if the view
+// containing the floating view is removed).
+//
+// To use it, you first need to create a view storage id that can then be used
+// to store/retrieve views.
+
+namespace views {
+
+struct ViewLocationInfo;
+
+class ViewStorage {
+ public:
+ // Returns the global ViewStorage instance.
+ // It is guaranted to be non NULL.
+ static ViewStorage* GetSharedInstance();
+
+ // Returns a unique storage id that can be used to store/retrieve views.
+ int CreateStorageID();
+
+ // Associates |view| with the specified |storage_id|.
+ void StoreView(int storage_id, View* view);
+
+ // Returns the view associated with |storage_id| if any, NULL otherwise.
+ View* RetrieveView(int storage_id);
+
+ // Removes the view associated with |storage_id| if any.
+ void RemoveView(int storage_id);
+
+ // Notifies the ViewStorage that a view was removed from its parent somewhere.
+ void ViewRemoved(View* parent, View* removed);
+
+#ifdef UNIT_TEST
+ size_t view_count() const { return view_to_ids_.size(); }
+#endif
+
+ private:
+ friend struct DefaultSingletonTraits<ViewStorage>;
+
+ ViewStorage();
+ ~ViewStorage();
+
+ // Removes the view associated with |storage_id|. If |remove_all_ids| is true,
+ // all other mapping pointing to the same view are removed as well.
+ void EraseView(int storage_id, bool remove_all_ids);
+
+ // Next id for the view storage.
+ int view_storage_next_id_;
+
+ // The association id to View used for the view storage. The ViewStorage owns
+ // the ViewLocationInfo.
+ std::map<int, ViewLocationInfo*> id_to_view_location_;
+
+ // Association View to id, used to speed up view notification removal.
+ std::map<View*, std::vector<int>*> view_to_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewStorage);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_FOCUS_VIEW_STORAGE_H_
diff --git a/views/grid_layout.cc b/views/grid_layout.cc
new file mode 100644
index 0000000..7eb07d1
--- /dev/null
+++ b/views/grid_layout.cc
@@ -0,0 +1,1013 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/grid_layout.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "views/view.h"
+
+namespace views {
+
+// 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 (typename 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 (typename 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 (typename 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 (typename 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 col_span;
+ const int row_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() {
+ gfx::Size 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;
+ if (!view_state->pref_width_fixed || !view_state->pref_height_fixed) {
+ pref = view_state->view->GetPreferredSize();
+ if (!view_state->pref_width_fixed)
+ view_state->pref_width = pref.width();
+ if (!view_state->pref_height_fixed)
+ view_state->pref_height = pref.height();
+ }
+ view_state->remaining_width = pref.width();
+ view_state->remaining_height = pref.height();
+ }
+
+ // 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.
+ gfx::Size pref;
+ SizeRowsAndColumns(true, host_->width(), host_->height(), &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);
+ }
+}
+
+gfx::Size GridLayout::GetPreferredSize(View* host) {
+ DCHECK(host_ == host);
+ gfx::Size out;
+ SizeRowsAndColumns(false, 0, 0, &out);
+ return out;
+}
+
+int GridLayout::GetPreferredHeightForWidth(View* host, int width) {
+ DCHECK(host_ == host);
+ gfx::Size pref;
+ SizeRowsAndColumns(false, width, 0, &pref);
+ return pref.height();
+}
+
+void GridLayout::SizeRowsAndColumns(bool layout, int width, int height,
+ gfx::Size* pref) {
+ // Make sure the master columns have been calculated.
+ CalculateMasterColumnsIfNecessary();
+ pref->SetSize(0, 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->set_width(std::max(pref->width(), (*i)->LayoutWidth()));
+ }
+ pref->set_width(pref->width() + 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->set_height(rows_[rows_.size() - 1]->Location() +
+ rows_[rows_.size() - 1]->Size() + top_inset_ + bottom_inset_);
+
+ if (layout && height != pref->height()) {
+ // We're doing a layout, and the height differs from the preferred height,
+ // divy up the extra space.
+ LayoutElement::DistributeDelta(height - pref->height(), &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 views
diff --git a/views/grid_layout.h b/views/grid_layout.h
new file mode 100644
index 0000000..e3e7a62
--- /dev/null
+++ b/views/grid_layout.h
@@ -0,0 +1,354 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_GRID_LAYOUT_H_
+#define VIEWS_GRID_LAYOUT_H_
+
+#include <string>
+#include <vector>
+
+#include "views/layout_manager.h"
+#include "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 views {
+
+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 gfx::Size GetPreferredSize(View* host);
+
+ 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,
+ gfx::Size* 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 views
+
+#endif // VIEWS_GRID_LAYOUT_H_
diff --git a/views/grid_layout_unittest.cc b/views/grid_layout_unittest.cc
new file mode 100644
index 0000000..57bfa83
--- /dev/null
+++ b/views/grid_layout_unittest.cc
@@ -0,0 +1,514 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/grid_layout.h"
+#include "views/view.h"
+
+using views::ColumnSet;
+using views::GridLayout;
+using views::View;
+
+static void ExpectViewBoundsEquals(int x, int y, int w, int h,
+ const View* view) {
+ EXPECT_EQ(x, view->x());
+ EXPECT_EQ(y, view->y());
+ EXPECT_EQ(w, view->width());
+ EXPECT_EQ(h, view->height());
+}
+
+class SettableSizeView : public View {
+ public:
+ explicit SettableSizeView(const gfx::Size& pref) {
+ pref_ = pref;
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return pref_;
+ }
+
+ private:
+ gfx::Size 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() {
+ pref = layout->GetPreferredSize(&host);
+ }
+
+ gfx::Size pref;
+ CRect bounds;
+ View host;
+ GridLayout* layout;
+};
+
+class GridLayoutAlignmentTest : public testing::Test {
+ public:
+ GridLayoutAlignmentTest() :
+ host(),
+ v1(gfx::Size(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);
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_TRUE(gfx::Size(10, 20) == pref);
+ host.SetBounds(0, 0, 100, 100);
+ layout->Layout(&host);
+ *bounds = v1.bounds().ToRECT();
+ 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(gfx::Size(10, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(30, 20) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 20, &v1);
+ ExpectViewBoundsEquals(10, 0, 20, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan1) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(100, 60) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(0, 20, 10, 40, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan2) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(100, 40) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(90, 20, 10, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan3) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 20));
+ SettableSizeView v3(gfx::Size(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(gfx::Size(100, 40) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ 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) {
+ views::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(gfx::Size(10, 10));
+ SettableSizeView v2(gfx::Size(10, 10));
+ SettableSizeView v3(gfx::Size(25, 20));
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+ layout->StartRow(0, 0);
+ layout->AddView(&v3, 2, 1);
+
+ GetPreferredSize();
+ EXPECT_TRUE(gfx::Size(25, 30) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ 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(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(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);
+
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_TRUE(gfx::Size(100, 20) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 50, 20, &v1);
+ ExpectViewBoundsEquals(50, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, HorizontalResizeTest1) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(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(gfx::Size(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(gfx::Size(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(gfx::Size(16, 24) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(2, 1, 10, 20, &v1);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, FixedSize) {
+ layout->SetInsets(2, 2, 2, 2);
+
+ views::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(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::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(gfx::Size(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(gfx::Size(column_count * title_width + 4,
+ row_count * pref_height + 4) == pref);
+}
+
+TEST_F(GridLayoutTest, RowSpanWithPaddingRow) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::FIXED,
+ 10,
+ 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 10)), 1, 2);
+ layout->AddPaddingRow(0, 10);
+}
+
+TEST_F(GridLayoutTest, RowSpan) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(20, 10)));
+ layout->AddView(new SettableSizeView(gfx::Size(20, 40)), 1, 2);
+ layout->StartRow(1, 0);
+ views::View* s3 = new SettableSizeView(gfx::Size(20, 10));
+ layout->AddView(s3);
+
+ GetPreferredSize();
+ EXPECT_TRUE(gfx::Size(40, 40) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 10, 20, 10, s3);
+}
+
+TEST_F(GridLayoutTest, RowSpan2) {
+ views::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(gfx::Size(20, 20)));
+ views::View* s3 = new SettableSizeView(gfx::Size(64, 64));
+ layout->AddView(s3, 1, 3);
+
+ layout->AddPaddingRow(0, 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 20)));
+
+ GetPreferredSize();
+ EXPECT_TRUE(gfx::Size(84, 64) == pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(20, 0, 64, 64, s3);
+}
+
+TEST_F(GridLayoutTest, FixedViewWidth) {
+ views::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(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 10, 0);
+
+ GetPreferredSize();
+ EXPECT_EQ(10, pref.width());
+ EXPECT_EQ(40, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 40, view);
+}
+
+TEST_F(GridLayoutTest, FixedViewHeight) {
+ views::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(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 0, 10);
+
+ GetPreferredSize();
+ EXPECT_EQ(30, pref.width());
+ EXPECT_EQ(10, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 30, 10, view);
+}
diff --git a/views/layout_manager.cc b/views/layout_manager.cc
new file mode 100644
index 0000000..800dbc2
--- /dev/null
+++ b/views/layout_manager.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/layout_manager.h"
+
+#include "views/view.h"
+
+namespace views {
+
+int LayoutManager::GetPreferredHeightForWidth(View* host, int width) {
+ return GetPreferredSize(host).height();
+}
+
+} // namespace views
diff --git a/views/layout_manager.h b/views/layout_manager.h
new file mode 100644
index 0000000..48a9c96
--- /dev/null
+++ b/views/layout_manager.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_LAYOUT_MANAGER_H_
+#define VIEWS_LAYOUT_MANAGER_H_
+
+#include "views/view.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace views {
+
+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 gfx::Size GetPreferredSize(View* host) = 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) {}
+};
+
+} // namespace views
+
+#endif // VIEWS_LAYOUT_MANAGER_H_
diff --git a/views/painter.cc b/views/painter.cc
new file mode 100644
index 0000000..a2ad4ee
--- /dev/null
+++ b/views/painter.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/painter.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/resource_bundle.h"
+#include "base/logging.h"
+#include "skia/include/SkBitmap.h"
+#include "skia/include/SkGradientShader.h"
+
+namespace views {
+
+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_);
+}
+
+} // namespace views
diff --git a/views/painter.h b/views/painter.h
new file mode 100644
index 0000000..83610c3
--- /dev/null
+++ b/views/painter.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_PAINTER_H_
+#define VIEWS_PAINTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "skia/include/SkColor.h"
+
+class ChromeCanvas;
+class SkBitmap;
+
+namespace views {
+
+// 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 height() 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);
+};
+
+} // namespace views
+
+#endif // VIEWS_PAINTER_H_
diff --git a/views/repeat_controller.cc b/views/repeat_controller.cc
new file mode 100644
index 0000000..bb70852
--- /dev/null
+++ b/views/repeat_controller.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/repeat_controller.h"
+
+using base::TimeDelta;
+
+namespace views {
+
+// 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)
+ : callback_(callback) {
+}
+
+RepeatController::~RepeatController() {
+}
+
+void RepeatController::Start() {
+ // The first timer is slightly longer than subsequent repeats.
+ timer_.Start(TimeDelta::FromMilliseconds(kInitialRepeatDelay), this,
+ &RepeatController::Run);
+}
+
+void RepeatController::Stop() {
+ timer_.Stop();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RepeatController, private:
+
+void RepeatController::Run() {
+ timer_.Start(TimeDelta::FromMilliseconds(kRepeatDelay), this,
+ &RepeatController::Run);
+ callback_->Run();
+}
+
+} // namespace views
diff --git a/views/repeat_controller.h b/views/repeat_controller.h
new file mode 100644
index 0000000..9e01dd1
--- /dev/null
+++ b/views/repeat_controller.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_REPEAT_CONTROLLER_H_
+#define VIEWS_REPEAT_CONTROLLER_H_
+
+#include "base/scoped_ptr.h"
+#include "base/timer.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 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:
+ 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();
+
+ private:
+ RepeatController();
+
+ // Called when the timer expires.
+ void Run();
+
+ // The current timer.
+ base::OneShotTimer<RepeatController> timer_;
+
+ scoped_ptr<RepeatCallback> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(RepeatController);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_REPEAT_CONTROLLER_H_
diff --git a/views/view.cc b/views/view.cc
new file mode 100644
index 0000000..cb73d74
--- /dev/null
+++ b/views/view.cc
@@ -0,0 +1,1643 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/view.h"
+
+#include <algorithm>
+#ifndef NDEBUG
+#include <iostream>
+#endif
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/scoped_handle.h"
+#include "base/string_util.h"
+#include "skia/include/SkShader.h"
+#include "views/background.h"
+#include "views/layout_manager.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+#include "views/window/window.h"
+#if defined(OS_WIN)
+#include "views/widget/tooltip_manager.h"
+#include "views/accessibility/view_accessibility_wrapper.h"
+#endif
+
+namespace views {
+
+// static
+char View::kViewClassName[] = "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_COPY_AND_ASSIGN(RestoreFocusTask);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// View - constructors, destructors, initialization
+//
+/////////////////////////////////////////////////////////////////////////////
+
+View::View()
+ : id_(0),
+ group_(-1),
+ enabled_(true),
+ focusable_(false),
+ bounds_(0, 0, 0, 0),
+ parent_(NULL),
+ should_restore_focus_(false),
+ is_visible_(true),
+ 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),
+ restore_focus_view_task_(NULL),
+ context_menu_controller_(NULL),
+#if defined(OS_WIN)
+ accessibility_(NULL),
+#endif
+ 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);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// View - sizing
+//
+/////////////////////////////////////////////////////////////////////////////
+
+gfx::Rect View::GetBounds(PositionMirroringSettings settings) const {
+ gfx::Rect bounds(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)
+ bounds.set_x(MirroredX());
+
+ return bounds;
+}
+
+// y(), width() and height() are agnostic to the RTL UI layout of the
+// parent view. x(), on the other hand, is not.
+int View::GetX(PositionMirroringSettings settings) const {
+ return settings == IGNORE_MIRRORING_TRANSFORMATION ? x() : MirroredX();
+}
+
+void View::SetBounds(const gfx::Rect& bounds) {
+ if (bounds == bounds_)
+ return;
+
+ gfx::Rect prev = bounds_;
+ bounds_ = bounds;
+ DidChangeBounds(prev, bounds_);
+
+ RootView* root = GetRootView();
+ if (root) {
+ bool size_changed = prev.size() != bounds_.size();
+ bool position_changed = prev.origin() != bounds_.origin();
+ if (size_changed || position_changed)
+ root->ViewBoundsChanged(this, size_changed, position_changed);
+ }
+}
+
+gfx::Rect View::GetLocalBounds(bool include_border) const {
+ if (include_border || !border_.get())
+ return gfx::Rect(0, 0, width(), height());
+
+ gfx::Insets insets;
+ border_->GetInsets(&insets);
+ return gfx::Rect(insets.left(), insets.top(),
+ std::max(0, width() - insets.width()),
+ std::max(0, height() - insets.height()));
+}
+
+gfx::Point View::GetPosition() const {
+ return gfx::Point(GetX(APPLY_MIRRORING_TRANSFORMATION), y());
+}
+
+gfx::Size View::GetPreferredSize() {
+ if (layout_manager_.get())
+ return layout_manager_->GetPreferredSize(this);
+ return gfx::Size();
+}
+
+void View::SizeToPreferredSize() {
+ gfx::Size prefsize = GetPreferredSize();
+ if ((prefsize.width() != width()) || (prefsize.height() != height()))
+ SetBounds(x(), y(), prefsize.width(), prefsize.height());
+}
+
+gfx::Size View::GetMinimumSize() {
+ return GetPreferredSize();
+}
+
+int View::GetHeightForWidth(int w) {
+ if (layout_manager_.get())
+ return layout_manager_->GetPreferredHeightForWidth(this, w);
+ return GetPreferredSize().height();
+}
+
+void View::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ Layout();
+}
+
+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, View::y() + y, width, height);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// View - layout
+//
+/////////////////////////////////////////////////////////////////////////////
+
+void View::Layout() {
+ // Layout child Views
+ if (layout_manager_.get()) {
+ layout_manager_->Layout(this);
+ SchedulePaint();
+ // TODO(beng): We believe the right thing to do here is return since the
+ // layout manager should be handling things, but it causes
+ // regressions (missing options from Options dialog and a hang
+ // in interactive_ui_tests).
+ }
+
+ // 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);
+ }
+}
+
+bool View::UILayoutIsRightToLeft() const {
+ return (ui_mirroring_is_enabled_for_rtl_languages_ &&
+ l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// View - Right-to-left UI layout
+//
+////////////////////////////////////////////////////////////////////////////////
+
+inline int View::MirroredX() const {
+ // TODO(beng): reimplement in terms of MirroredLeftPointForRect.
+ View* parent = GetParent();
+ if (parent && parent->UILayoutIsRightToLeft())
+ return parent->width() - x() - width();
+ return x();
+}
+
+int View::MirroredLeftPointForRect(const gfx::Rect& bounds) const {
+ if (!UILayoutIsRightToLeft()) {
+ return bounds.x();
+ }
+ return width() - 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;
+}
+
+bool View::HasFocus() {
+ 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 gfx::Rect& 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.
+ gfx::Rect paint_rect = r;
+ paint_rect.Offset(GetPosition());
+ parent_->SchedulePaint(paint_rect, urgent);
+ }
+}
+
+void View::SchedulePaint() {
+ SchedulePaint(GetLocalBounds(true), false);
+}
+
+void View::SchedulePaint(int x, int y, int w, int h) {
+ SchedulePaint(gfx::Rect(x, y, w, h), false);
+}
+
+void View::Paint(ChromeCanvas* canvas) {
+ PaintBackground(canvas);
+ PaintFocusBorder(canvas);
+ PaintBorder(canvas);
+}
+
+void View::PaintBackground(ChromeCanvas* canvas) {
+ if (background_.get())
+ background_->Paint(canvas, this);
+}
+
+void View::PaintBorder(ChromeCanvas* canvas) {
+ if (border_.get())
+ border_->Paint(*this, canvas);
+}
+
+void View::PaintFocusBorder(ChromeCanvas* canvas) {
+ if (HasFocus() && IsFocusable())
+ canvas->DrawFocusRect(0, 0, width(), height());
+}
+
+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(), y(), width(), 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(), y());
+
+ // 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(width(), 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);
+}
+
+gfx::Insets View::GetInsets() const {
+ gfx::Insets insets;
+ if (border_.get())
+ border_->GetInsets(&insets);
+ return insets;
+}
+
+void View::SetContextMenuController(ContextMenuController* menu_controller) {
+ context_menu_controller_ = menu_controller;
+}
+
+void View::ShowContextMenu(int x, int y, bool is_mouse_gesture) {
+ if (!context_menu_controller_)
+ return;
+
+ context_menu_controller_->ShowContextMenu(this, x, y, is_mouse_gesture);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// View - tree
+//
+/////////////////////////////////////////////////////////////////////////////
+
+bool View::ProcessMousePressed(const MouseEvent& e, DragInfo* drag_info) {
+ const bool enabled = enabled_;
+ int drag_operations;
+ if (enabled && e.IsOnlyLeftMouseButton() && HitTest(e.location()))
+ drag_operations = GetDragOperations(e.x(), e.y());
+ else
+ drag_operations = 0;
+ ContextMenuController* context_menu_controller =
+ e.IsRightMouseButton() ? context_menu_controller_ : 0;
+
+ 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.x(), e.y());
+ 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.x(),
+ drag_info->start_y - e.y())) {
+ 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.
+ gfx::Point location(e.location());
+ OnMouseReleased(e, canceled);
+ if (HitTest(location)) {
+ ConvertPointToScreen(this, &location);
+ ShowContextMenu(location.x(), location.y(), true);
+ }
+ } else {
+ OnMouseReleased(e, canceled);
+ }
+ // WARNING: we may have been deleted.
+}
+
+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);
+}
+
+void View::ThemeChanged() {
+ int c = GetChildViewCount();
+ for (int i = c - 1; i >= 0; --i)
+ GetChildViewAt(i)->ThemeChanged();
+}
+
+#ifndef NDEBUG
+bool View::IsProcessingPaint() const {
+ return GetParent() && GetParent()->IsProcessingPaint();
+}
+#endif
+
+gfx::Point View::GetKeyboardContextMenuLocation() {
+ gfx::Rect vis_bounds = GetVisibleBounds();
+ gfx::Point screen_point(vis_bounds.x() + vis_bounds.width() / 2,
+ vis_bounds.y() + vis_bounds.height() / 2);
+ ConvertPointToScreen(this, &screen_point);
+ return screen_point;
+}
+
+bool View::HasHitTestMask() const {
+ return false;
+}
+
+void View::GetHitTestMask(gfx::Path* mask) const {
+ DCHECK(mask);
+}
+
+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 gfx::Point& 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 gfx::Point& 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;
+
+ gfx::Point point_in_child_coords(point);
+ View::ConvertPointToView(this, child, &point_in_child_coords);
+ if (child->HitTest(point_in_child_coords))
+ return child->GetViewForPoint(point_in_child_coords, 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;
+}
+
+Widget* View::GetWidget() const {
+ // The root view holds a reference to this view hierarchy's Widget.
+ return parent_ ? parent_->GetWidget() : NULL;
+}
+
+Window* View::GetWindow() const {
+ Widget* widget = GetWidget();
+ return widget ? widget->GetWindow() : NULL;
+}
+
+// Get the containing RootView
+RootView* View::GetRootView() {
+ Widget* widget = GetWidget();
+ return widget ? widget->GetRootView() : 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_.x() << L"," << bounds_.y() << 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::RemoveAccelerator(const Accelerator& accelerator) {
+ std::vector<Accelerator>::iterator iter;
+ if (!accelerators_.get() ||
+ ((iter = std::find(accelerators_->begin(), accelerators_->end(),
+ accelerator)) == accelerators_->end())) {
+ NOTREACHED() << "Removing non-existing accelerator";
+ return;
+ }
+
+ accelerators_->erase(iter);
+ RootView* root_view = GetRootView();
+ if (!root_view) {
+ // We are not part of a view hierarchy, so there is nothing to do as we
+ // removed ourselves from accelerators_, we won't be registered when added
+ // to one.
+ return;
+ }
+
+ // TODO(port): Fix this once we have a FocusManger for Linux.
+#if defined(OS_WIN)
+ 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->UnregisterAccelerator(accelerator, this);
+ }
+#endif
+}
+
+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;
+ }
+
+ // TODO(port): Fix this once we have a FocusManger for Linux.
+#if defined(OS_WIN)
+ 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);
+ }
+#endif
+}
+
+void View::UnregisterAccelerators() {
+ if (!accelerators_.get())
+ return;
+
+ RootView* root_view = GetRootView();
+ if (root_view) {
+ // TODO(port): Fix this once we have a FocusManger for Linux.
+#if defined(OS_WIN)
+ 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);
+ }
+#endif
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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->y(),
+ v->width(), v->height());
+ 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)) {
+ // TODO(port): Fix this once we have a FocusManger for Linux.
+#if defined(OS_WIN)
+ focus_manager->StoreFocusedView();
+#endif
+ 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;
+
+ // TODO(port): Fix this once we have a FocusManger for Linux.
+#if defined(OS_WIN)
+ FocusManager* focus_manager = GetFocusManager();
+ DCHECK(focus_manager);
+ if (focus_manager)
+ focus_manager->RestoreFocusedView();
+#endif
+}
+
+// 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(const View* src,
+ const View* dst,
+ gfx::Point* point) {
+ ConvertPointToView(src, dst, point, true);
+}
+
+// static
+void View::ConvertPointToView(const View* src,
+ const View* dst,
+ gfx::Point* point,
+ bool try_other_direction) {
+ // src can be NULL
+ DCHECK(dst);
+ DCHECK(point);
+
+ const 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->y());
+ }
+
+ // 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) {
+ Widget* widget = dst->GetWidget();
+ if (widget) {
+ gfx::Rect b;
+ widget->GetBounds(&b, false);
+ point->SetPoint(point->x() - b.x(), point->y() - b.y());
+ }
+ }
+ }
+}
+
+// static
+void View::ConvertPointToWidget(const View* src, gfx::Point* p) {
+ DCHECK(src);
+ DCHECK(p);
+
+ gfx::Point offset;
+ for (const View* v = src; v; v = v->GetParent()) {
+ offset.set_x(offset.x() + v->GetX(APPLY_MIRRORING_TRANSFORMATION));
+ offset.set_y(offset.y() + v->y());
+ }
+ p->SetPoint(p->x() + offset.x(), p->y() + offset.y());
+}
+
+// static
+void View::ConvertPointFromWidget(const View* dest, gfx::Point* p) {
+ gfx::Point t;
+ ConvertPointToWidget(dest, &t);
+ p->SetPoint(p->x() - t.x(), p->y() - t.y());
+}
+
+// static
+void View::ConvertPointToScreen(const View* src, gfx::Point* p) {
+ DCHECK(src);
+ DCHECK(p);
+
+ // If the view is not connected to a tree, there's nothing we can do.
+ Widget* widget = src->GetWidget();
+ if (widget) {
+ ConvertPointToWidget(src, p);
+ gfx::Rect r;
+ widget->GetBounds(&r, false);
+ p->SetPoint(p->x() + r.x(), p->y() + r.y());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// View - keyboard and focus
+//
+/////////////////////////////////////////////////////////////////////////////
+
+void View::RequestFocus() {
+ RootView* rv = GetRootView();
+ if (rv && IsFocusable())
+ 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
+bool View::ExceededDragThreshold(int delta_x, int delta_y) {
+ return (abs(delta_x) > GetHorizontalDragThreshold() ||
+ abs(delta_y) > GetVerticalDragThreshold());
+}
+
+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, gfx::Point* loc) {
+ return false;
+}
+
+void View::TooltipTextChanged() {
+#if defined(OS_WIN)
+ Widget* widget = GetWidget();
+ if (widget && widget->GetTooltipManager())
+ widget->GetTooltipManager()->TooltipTextChanged(this);
+#else
+ // TODO(port): Not actually windows specific; I just haven't ported this part
+ // yet.
+ NOTIMPLEMENTED();
+#endif
+}
+
+void View::UpdateTooltip() {
+#if defined(OS_WIN)
+ Widget* widget = GetWidget();
+ if (widget && widget->GetTooltipManager())
+ widget->GetTooltipManager()->UpdateTooltip();
+#else
+ // TODO(port): Not actually windows specific; I just haven't ported this part
+ // yet.
+ NOTIMPLEMENTED();
+#endif
+}
+
+void View::SetParentOwned(bool f) {
+ is_parent_owned_ = f;
+}
+
+bool View::IsParentOwned() const {
+ return is_parent_owned_;
+}
+
+std::string View::GetClassName() const {
+ return kViewClassName;
+}
+
+View* View::GetAncestorWithClassName(const std::string& name) {
+ for (View* view = this; view; view = view->GetParent()) {
+ if (view->GetClassName() == name)
+ return view;
+ }
+ return NULL;
+}
+
+gfx::Rect View::GetVisibleBounds() {
+ if (!IsVisibleInRootView())
+ return gfx::Rect();
+ gfx::Rect vis_bounds(0, 0, width(), height());
+ gfx::Rect ancestor_bounds;
+ View* view = this;
+ int root_x = 0;
+ int root_y = 0;
+ while (view != NULL && !vis_bounds.IsEmpty()) {
+ root_x += view->GetX(APPLY_MIRRORING_TRANSFORMATION);
+ root_y += view->y();
+ vis_bounds.Offset(view->GetX(APPLY_MIRRORING_TRANSFORMATION), view->y());
+ View* ancestor = view->GetParent();
+ if (ancestor != NULL) {
+ ancestor_bounds.SetRect(0, 0, ancestor->width(),
+ ancestor->height());
+ vis_bounds = vis_bounds.Intersect(ancestor_bounds);
+ } else if (!view->GetWidget()) {
+ // If the view has no Widget, 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/views/view.h b/views/view.h
new file mode 100644
index 0000000..d2d8e52
--- /dev/null
+++ b/views/view.h
@@ -0,0 +1,1351 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_VIEW_H_
+#define VIEWS_VIEW_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlmisc.h>
+#endif // defined(OS_WIN)
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/gfx/rect.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/accessibility_types.h"
+#include "views/accelerator.h"
+#include "views/background.h"
+#include "views/border.h"
+
+namespace gfx {
+class Insets;
+class Path;
+}
+
+class ChromeCanvas;
+class OSExchangeData;
+class ViewAccessibilityWrapper;
+
+namespace views {
+
+class Background;
+class Border;
+class FocusManager;
+class FocusTraversable;
+class LayoutManager;
+class RestoreFocusTask;
+class RootView;
+class ScrollView;
+class Widget;
+class Window;
+
+// 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 views 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 x() 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()).
+ // TODO(beng): Convert |bounds_| to a gfx::Rect.
+ gfx::Rect bounds() const { return bounds_; }
+
+ // Get the size of the View.
+ gfx::Size size() const { return bounds_.size(); }
+
+ // 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.
+ gfx::Rect GetBounds(PositionMirroringSettings settings) const;
+
+ // Set the bounds in the parent's coordinate system.
+ void SetBounds(const gfx::Rect& bounds);
+ void SetBounds(int x, int y, int width, int height) {
+ SetBounds(gfx::Rect(x, y, std::max(0, width), std::max(0, height)));
+ }
+ void SetX(int x) { SetBounds(x, y(), width(), height()); }
+ void SetY(int y) { SetBounds(x(), y, width(), height()); }
+
+ // Returns the left coordinate of the View, relative to the parent View,
+ // which is the value of bounds_.x().
+ //
+ // 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()).
+ // This is equivalent to GetX(IGNORE_MIRRORING_TRANSFORMATION), but
+ // inlinable.
+ int x() const { return bounds_.x(); }
+ int y() const { return bounds_.y(); }
+ int width() const { return bounds_.width(); }
+ int height() const { return bounds_.height(); }
+
+ // Return the left coordinate of the View, relative to the parent. If
+ // |settings| is IGNORE_MIRRORING_SETTINGS, the function returns the value of
+ // bounds_.x(). 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_.x().
+ //
+ // 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 x() when you need to get the X
+ // coordinate of a child View.
+ int GetX(PositionMirroringSettings settings) const;
+
+ // Return this control local bounds. If include_border is true, local bounds
+ // is the rectangle {0, 0, width(), height()}, otherwise, it does not
+ // include the area where the border (if any) is painted.
+ gfx::Rect GetLocalBounds(bool include_border) 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 x()/y() if you want to ignore
+ // mirroring.
+ gfx::Point GetPosition() const;
+
+ // Get the size the View would like to be, if enough space were available.
+ virtual gfx::Size GetPreferredSize();
+
+ // 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 gfx::Size GetMinimumSize();
+
+ // 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 gfx::Rect& previous,
+ const gfx::Rect& 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;
+
+ // 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
+ // (views::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.
+ int 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() ? width() - 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 gfx::Rect& 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 gfx::Point& point);
+
+ // Get the Widget that hosts this View, if any.
+ virtual Widget* GetWidget() const;
+
+ // Gets the Widget that most closely contains this View, if any.
+ virtual Window* GetWindow() 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
+ // Widget that contains this view. This can return NULL if this view is not
+ // part of a view hierarchy with a Widget.
+ 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 the specified accelerator for this view.
+ virtual void RemoveAccelerator(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 accessibility 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 Role appropriately, and returns true if
+ // successful.
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role) {
+ return false;
+ }
+
+ // Returns the accessibility state of the current view. Sets the input State
+ // appropriately, and returns true if successful.
+ virtual bool GetAccessibleState(AccessibilityTypes::State* 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.
+ ViewAccessibilityWrapper* GetViewAccessibilityWrapper();
+
+ // 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 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(const View* src,
+ const View* dst,
+ gfx::Point* point);
+
+ // Convert a point from the coordinate system of a View to that of the
+ // Widget. This is useful for example when sizing HWND children of the
+ // Widget that don't know about the View hierarchy and need to be placed
+ // relative to the Widget that is their parent.
+ static void ConvertPointToWidget(const View* src, gfx::Point* point);
+
+ // Convert a point from a view Widget to a View dest
+ static void ConvertPointFromWidget(const View* dest, gfx::Point* 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(const View* src, gfx::Point* 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_;
+ }
+
+ // Provides default implementation for context menu handling. The default
+ // implementation calls the ShowContextMenu of the current
+ // ContextMenuController (if it is not NULL). Overridden in subclassed views
+ // to provide right-click menu display triggerd by the keyboard (i.e. for the
+ // Chrome toolbar Back and Forward buttons). No source needs to be specified,
+ // as it is always equal to the current View.
+ virtual void ShowContextMenu(int x,
+ int y,
+ bool is_mouse_gesture);
+
+ // The background object is owned by this object and may be NULL.
+ void set_background(Background* b) { background_.reset(b); }
+ const Background* background() const { return background_.get(); }
+
+ // The border object is owned by this object and may be NULL.
+ void set_border(Border* b) { border_.reset(b); }
+ const Border* border() const { return border_.get(); }
+
+ // Returns the insets of the current border. If there is no border an empty
+ // insets is returned.
+ virtual gfx::Insets GetInsets() const;
+
+#if defined(OS_WIN)
+ // TODO(port): Make GetCursorForPoint portable.
+
+ // 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);
+#endif // defined(OS_WIN)
+
+ // Convenience to test whether a point is within this view's bounds
+ virtual bool HitTest(const gfx::Point& 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, gfx::Point* 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 first ancestor, starting at this, whose class name is |name|.
+ // Returns null if no ancestor has the class name |name|.
+ View* GetAncestorWithClassName(const std::string& name);
+
+ // 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:
+ // 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_;
+
+ // Called when the UI theme has changed, overriding allows individual Views to
+ // do special cleanup and processing (such as dropping resource caches).
+ // Subclasses that override this method must call the base class
+ // implementation to ensure child views are processed.
+ // Can only be called by subclasses. To dispatch a theme changed notification,
+ // call this method on the RootView.
+ virtual void ThemeChanged();
+
+#ifndef NDEBUG
+ // Returns true if the View is currently processing a paint.
+ virtual bool IsProcessingPaint() const;
+#endif
+
+ // Returns the location, in screen coordinates, to show the context menu at
+ // when the context menu is shown from the keyboard. This implementation
+ // returns the middle of the visible region of this view.
+ //
+ // This method is invoked when the context menu is shown by way of the
+ // keyboard.
+ virtual gfx::Point GetKeyboardContextMenuLocation();
+
+ // Called by HitTest to see if this View has a custom hit test mask. If the
+ // return value is true, GetHitTestMask will be called to obtain the mask.
+ // Default value is false, in which case the View will hit-test against its
+ // bounds.
+ virtual bool HasHitTestMask() const;
+
+ // Called by HitTest to retrieve a mask for hit-testing against. Subclasses
+ // override to provide custom shaped hit test regions.
+ virtual void GetHitTestMask(gfx::Path* mask) const;
+
+ // 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 gfx::Point& 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
+ // Widget, 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();
+
+ // 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;
+ };
+
+ // Returns how much the mouse needs to move in one direction to start a
+ // drag. These methods cache in a platform-appropriate way. These values are
+ // used by the public static method ExceededDragThreshold().
+ static int GetHorizontalDragThreshold();
+ static int GetVerticalDragThreshold();
+
+ // 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 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(const View* src,
+ const View* dst,
+ gfx::Point* point,
+ bool try_other_direction);
+
+ // Propagates UpdateTooltip() to the TooltipManager for the Widget.
+ // 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 bounds in the parent coordinate system.
+ gfx::Rect bounds_;
+
+ // 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
+ scoped_ptr<Background> background_;
+
+ // Border.
+ scoped_ptr<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_;
+
+#if defined(OS_WIN)
+ // The accessibility implementation for this View.
+ scoped_ptr<ViewAccessibilityWrapper> accessibility_;
+#endif
+
+ 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_COPY_AND_ASSIGN(View);
+};
+
+} // namespace views
+
+#endif // VIEWS_VIEW_H_
diff --git a/views/view_constants.cc b/views/view_constants.cc
new file mode 100644
index 0000000..0e2ed17
--- /dev/null
+++ b/views/view_constants.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/view_constants.h"
+
+namespace views {
+
+const int kAutoscrollSize = 10;
+const int kAutoscrollRowTimerMS = 200;
+const int kDropBetweenPixels = 5;
+
+} // namespace views
diff --git a/views/view_constants.h b/views/view_constants.h
new file mode 100644
index 0000000..d36dbb4
--- /dev/null
+++ b/views/view_constants.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_VIEW_CONSTANTS_H_
+#define VIEWS_VIEW_CONSTANTS_H_
+
+namespace views {
+
+// Size (width or height) within which the user can hold the mouse and the
+// view should scroll.
+extern const int kAutoscrollSize;
+
+// Time in milliseconds to autoscroll by a row. This is used during drag and
+// drop.
+extern const int kAutoscrollRowTimerMS;
+
+// 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 item, otherwise it is on the item.
+extern const int kDropBetweenPixels;
+
+} // namespace views
+
+#endif // VIEWS_VIEW_CONSTANTS_H_
diff --git a/views/view_gtk.cc b/views/view_gtk.cc
new file mode 100644
index 0000000..61ca6ca
--- /dev/null
+++ b/views/view_gtk.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/view.h"
+
+#include "base/logging.h"
+
+namespace views {
+
+FocusManager* View::GetFocusManager() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void View::DoDrag(const MouseEvent& e, int press_x, int press_y) {
+ NOTIMPLEMENTED();
+}
+
+ViewAccessibilityWrapper* View::GetViewAccessibilityWrapper() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+bool View::HitTest(const gfx::Point& l) const {
+ if (l.x() >= 0 && l.x() < static_cast<int>(width()) &&
+ l.y() >= 0 && l.y() < static_cast<int>(height())) {
+ if (HasHitTestMask()) {
+ // TODO(port): port the windows hit test code here. Once that's factored
+ // out, we can probably move View::HitTest back into views.cc.
+ NOTIMPLEMENTED();
+ }
+ // No mask, but inside our bounds.
+ return true;
+ }
+ // Outside our bounds.
+ return false;
+}
+
+void View::Focus() {
+ NOTIMPLEMENTED();
+}
+
+int View::GetHorizontalDragThreshold() {
+ static int threshold = -1;
+ NOTIMPLEMENTED();
+ return threshold;
+}
+
+int View::GetVerticalDragThreshold() {
+ static int threshold = -1;
+ NOTIMPLEMENTED();
+ return threshold;
+}
+
+} // namespace views
diff --git a/views/view_unittest.cc b/views/view_unittest.cc
new file mode 100644
index 0000000..d509940e
--- /dev/null
+++ b/views/view_unittest.cc
@@ -0,0 +1,1009 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/path.h"
+#include "base/clipboard.h"
+#include "base/message_loop.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/common/notification_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/background.h"
+#include "views/controls/button/checkbox.h"
+#if defined(OS_WIN)
+#include "views/controls/button/native_button_win.h"
+#endif
+#include "views/controls/scroll_view.h"
+#include "views/controls/text_field.h"
+#include "views/event.h"
+#include "views/focus/view_storage.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+#include "views/window/dialog_delegate.h"
+#include "views/window/window.h"
+
+using namespace views;
+
+namespace {
+
+class ViewTest : public testing::Test {
+ public:
+ ViewTest() {
+ OleInitialize(NULL);
+ }
+
+ ~ViewTest() {
+ OleUninitialize();
+ }
+
+ private:
+ MessageLoopForUI message_loop_;
+};
+
+// Paints the RootView.
+void PaintRootView(views::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.
+ gfx::Rect paint_rect = root->GetScheduledPaintRect();
+ ChromeCanvas canvas(paint_rect.width(), paint_rect.height(), true);
+ canvas.TranslateInt(-paint_rect.x(), -paint_rect.y());
+ 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);
+};
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A view subclass for testing purpose
+//
+////////////////////////////////////////////////////////////////////////////////
+class TestView : public View {
+ public:
+ TestView() : View(){
+ }
+
+ 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 gfx::Rect& previous,
+ const gfx::Rect& 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_;
+ gfx::Rect previous_bounds_;
+ gfx::Rect 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 gfx::Rect& previous,
+ const gfx::Rect& current) {
+ did_change_bounds_ = true;
+ previous_bounds_ = previous;
+ new_bounds_ = current;
+}
+
+TEST_F(ViewTest, DidChangeBounds) {
+ TestView* v = new TestView();
+
+ gfx::Rect prev_rect(0, 0, 200, 200);
+ gfx::Rect 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);
+
+ EXPECT_EQ(v->bounds(), gfx::Rect(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.x();
+ location_.y = event.y();
+ return true;
+}
+
+bool TestView::OnMouseDragged(const MouseEvent& event) {
+ last_mouse_event_type_ = event.GetType();
+ location_.x = event.x();
+ location_.y = event.y();
+ return true;
+}
+
+void TestView::OnMouseReleased(const MouseEvent& event, bool canceled) {
+ last_mouse_event_type_ = event.GetType();
+ location_.x = event.x();
+ location_.y = event.y();
+}
+
+TEST_F(ViewTest, MouseEvent) {
+ TestView* v1 = new TestView();
+ v1->SetBounds(0, 0, 300, 300);
+
+ TestView* v2 = new TestView();
+ v2->SetBounds (100, 100, 100, 100);
+
+ views::WidgetWin window;
+ window.set_delete_on_destroy(false);
+ window.set_window_style(WS_OVERLAPPEDWINDOW);
+ window.Init(NULL, gfx::Rect(50, 50, 650, 650), false);
+ 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, DISABLED_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();
+
+ views::WidgetWin window;
+ window.set_delete_on_destroy(false);
+ window.set_window_style(WS_OVERLAPPEDWINDOW);
+ window.Init(NULL, gfx::Rect(50, 50, 650, 650), 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();
+}
+*/
+
+TEST_F(ViewTest, RemoveNotification) {
+ views::ViewStorage* vs = views::ViewStorage::GetSharedInstance();
+ views::WidgetWin* window = new views::WidgetWin;
+ views::RootView* root_view = window->GetRootView();
+
+ View* v1 = new View;
+ int s1 = vs->CreateStorageID();
+ vs->StoreView(s1, v1);
+ root_view->AddChildView(v1);
+ View* v11 = new View;
+ int s11 = vs->CreateStorageID();
+ vs->StoreView(s11, v11);
+ v1->AddChildView(v11);
+ View* v111 = new View;
+ int s111 = vs->CreateStorageID();
+ vs->StoreView(s111, v111);
+ v11->AddChildView(v111);
+ View* v112 = new View;
+ int s112 = vs->CreateStorageID();
+ vs->StoreView(s112, v112);
+ v11->AddChildView(v112);
+ View* v113 = new View;
+ int s113 = vs->CreateStorageID();
+ vs->StoreView(s113, v113);
+ v11->AddChildView(v113);
+ View* v1131 = new View;
+ int s1131 = vs->CreateStorageID();
+ vs->StoreView(s1131, v1131);
+ v113->AddChildView(v1131);
+ View* v12 = new View;
+ int s12 = vs->CreateStorageID();
+ vs->StoreView(s12, v12);
+ v1->AddChildView(v12);
+
+ View* v2 = new View;
+ int s2 = vs->CreateStorageID();
+ vs->StoreView(s2, v2);
+ root_view->AddChildView(v2);
+ View* v21 = new View;
+ int s21 = vs->CreateStorageID();
+ vs->StoreView(s21, v21);
+ v2->AddChildView(v21);
+ View* v211 = new View;
+ int s211 = vs->CreateStorageID();
+ vs->StoreView(s211, v211);
+ v21->AddChildView(v211);
+
+ size_t stored_views = vs->view_count();
+
+ // Try removing a leaf view.
+ v21->RemoveChildView(v211);
+ EXPECT_EQ(stored_views - 1, vs->view_count());
+ EXPECT_EQ(NULL, vs->RetrieveView(s211));
+ delete v211; // We won't use this one anymore.
+
+ // Now try removing a view with a hierarchy of depth 1.
+ v11->RemoveChildView(v113);
+ EXPECT_EQ(stored_views - 3, vs->view_count());
+ EXPECT_EQ(NULL, vs->RetrieveView(s113));
+ EXPECT_EQ(NULL, vs->RetrieveView(s1131));
+ delete v113; // We won't use this one anymore.
+
+ // Now remove even more.
+ root_view->RemoveChildView(v1);
+ EXPECT_EQ(stored_views - 8, vs->view_count());
+ EXPECT_EQ(NULL, vs->RetrieveView(s1));
+ EXPECT_EQ(NULL, vs->RetrieveView(s11));
+ EXPECT_EQ(NULL, vs->RetrieveView(s12));
+ EXPECT_EQ(NULL, vs->RetrieveView(s111));
+ EXPECT_EQ(NULL, vs->RetrieveView(s112));
+
+ // Put v1 back for more tests.
+ root_view->AddChildView(v1);
+ vs->StoreView(s1, v1);
+
+ // 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(stored_views - 10, vs->view_count());
+ EXPECT_EQ(NULL, vs->RetrieveView(s1));
+ EXPECT_EQ(NULL, vs->RetrieveView(s12));
+ EXPECT_EQ(NULL, vs->RetrieveView(s11));
+ EXPECT_EQ(NULL, vs->RetrieveView(s12));
+ EXPECT_EQ(NULL, vs->RetrieveView(s21));
+ EXPECT_EQ(NULL, vs->RetrieveView(s111));
+ EXPECT_EQ(NULL, vs->RetrieveView(s112));
+}
+
+namespace {
+class HitTestView : public views::View {
+ public:
+ explicit HitTestView(bool has_hittest_mask)
+ : has_hittest_mask_(has_hittest_mask) {
+ }
+ virtual ~HitTestView() {}
+
+ protected:
+ // Overridden from views::View:
+ virtual bool HasHitTestMask() const {
+ return has_hittest_mask_;
+ }
+ virtual void GetHitTestMask(gfx::Path* mask) const {
+ DCHECK(has_hittest_mask_);
+ DCHECK(mask);
+
+ SkScalar w = SkIntToScalar(width());
+ SkScalar h = SkIntToScalar(height());
+
+ // Create a triangular mask within the bounds of this View.
+ mask->moveTo(w / 2, 0);
+ mask->lineTo(w, h);
+ mask->lineTo(0, h);
+ mask->close();
+ }
+
+ private:
+ bool has_hittest_mask_;
+
+ DISALLOW_COPY_AND_ASSIGN(HitTestView);
+};
+
+gfx::Point ConvertPointToView(views::View* view, const gfx::Point& p) {
+ gfx::Point tmp(p);
+ views::View::ConvertPointToView(view->GetRootView(), view, &tmp);
+ return tmp;
+}
+}
+
+TEST_F(ViewTest, HitTestMasks) {
+ views::WidgetWin window;
+ views::RootView* root_view = window.GetRootView();
+ root_view->SetBounds(0, 0, 500, 500);
+
+ gfx::Rect v1_bounds = gfx::Rect(0, 0, 100, 100);
+ HitTestView* v1 = new HitTestView(false);
+ v1->SetBounds(v1_bounds);
+ root_view->AddChildView(v1);
+
+ gfx::Rect v2_bounds = gfx::Rect(105, 0, 100, 100);
+ HitTestView* v2 = new HitTestView(true);
+ v2->SetBounds(v2_bounds);
+ root_view->AddChildView(v2);
+
+ gfx::Point v1_centerpoint = v1_bounds.CenterPoint();
+ gfx::Point v2_centerpoint = v2_bounds.CenterPoint();
+ gfx::Point v1_origin = v1_bounds.origin();
+ gfx::Point v2_origin = v2_bounds.origin();
+
+ // Test HitTest
+ EXPECT_EQ(true, v1->HitTest(ConvertPointToView(v1, v1_centerpoint)));
+ EXPECT_EQ(true, v2->HitTest(ConvertPointToView(v2, v2_centerpoint)));
+
+ EXPECT_EQ(true, v1->HitTest(ConvertPointToView(v1, v1_origin)));
+ EXPECT_EQ(false, v2->HitTest(ConvertPointToView(v2, v2_origin)));
+
+ // Test GetViewForPoint
+ EXPECT_EQ(v1, root_view->GetViewForPoint(v1_centerpoint));
+ EXPECT_EQ(v2, root_view->GetViewForPoint(v2_centerpoint));
+ EXPECT_EQ(v1, root_view->GetViewForPoint(v1_origin));
+ EXPECT_EQ(root_view, root_view->GetViewForPoint(v2_origin));
+}
+
+#if defined(OS_WIN)
+// Tests that the TextField view respond appropiately to cut/copy/paste.
+TEST_F(ViewTest, TextFieldCutCopyPaste) {
+ const std::wstring kNormalText = L"Normal";
+ const std::wstring kReadOnlyText = L"Read only";
+ const std::wstring kPasswordText = L"Password! ** Secret stuff **";
+
+ Clipboard* clipboard = g_browser_process->clipboard();
+
+ WidgetWin* window = new WidgetWin;
+ window->Init(NULL, gfx::Rect(0, 0, 100, 100), true);
+ RootView* root_view = window->GetRootView();
+
+ TextField* normal = new TextField();
+ TextField* read_only = new TextField();
+ read_only->SetReadOnly(true);
+ TextField* password = new TextField(TextField::STYLE_PASSWORD);
+
+ root_view->AddChildView(normal);
+ root_view->AddChildView(read_only);
+ root_view->AddChildView(password);
+
+ normal->SetText(kNormalText);
+ read_only->SetText(kReadOnlyText);
+ password->SetText(kPasswordText);
+
+ //
+ // Test cut.
+ //
+ ASSERT_TRUE(normal->GetNativeComponent());
+ normal->SelectAll();
+ ::SendMessage(normal->GetNativeComponent(), WM_CUT, 0, 0);
+
+ string16 result;
+ clipboard->ReadText(&result);
+ EXPECT_EQ(kNormalText, result);
+ normal->SetText(kNormalText); // Let's revert to the original content.
+
+ ASSERT_TRUE(read_only->GetNativeComponent());
+ read_only->SelectAll();
+ ::SendMessage(read_only->GetNativeComponent(), WM_CUT, 0, 0);
+ result.clear();
+ clipboard->ReadText(&result);
+ // Cut should have failed, so the clipboard content should not have changed.
+ EXPECT_EQ(kNormalText, result);
+
+ ASSERT_TRUE(password->GetNativeComponent());
+ password->SelectAll();
+ ::SendMessage(password->GetNativeComponent(), WM_CUT, 0, 0);
+ result.clear();
+ clipboard->ReadText(&result);
+ // Cut should have failed, so the clipboard content should not have changed.
+ EXPECT_EQ(kNormalText, result);
+
+ //
+ // Test copy.
+ //
+
+ // Let's start with read_only as the clipboard already contains the content
+ // of normal.
+ read_only->SelectAll();
+ ::SendMessage(read_only->GetNativeComponent(), WM_COPY, 0, 0);
+ result.clear();
+ clipboard->ReadText(&result);
+ EXPECT_EQ(kReadOnlyText, result);
+
+ normal->SelectAll();
+ ::SendMessage(normal->GetNativeComponent(), WM_COPY, 0, 0);
+ result.clear();
+ clipboard->ReadText(&result);
+ EXPECT_EQ(kNormalText, result);
+
+ password->SelectAll();
+ ::SendMessage(password->GetNativeComponent(), WM_COPY, 0, 0);
+ result.clear();
+ clipboard->ReadText(&result);
+ // We don't let you copy from a password field, clipboard should not have
+ // changed.
+ EXPECT_EQ(kNormalText, result);
+
+ //
+ // Test Paste.
+ //
+ // Note that we use GetWindowText instead of TextField::GetText below as the
+ // text in the TextField class is synced to the text of the HWND on
+ // WM_KEYDOWN messages that we are not simulating here.
+
+ // Attempting to copy kNormalText in a read-only text-field should fail.
+ read_only->SelectAll();
+ ::SendMessage(read_only->GetNativeComponent(), WM_KEYDOWN, 0, 0);
+ wchar_t buffer[1024] = { 0 };
+ ::GetWindowText(read_only->GetNativeComponent(), buffer, 1024);
+ EXPECT_EQ(kReadOnlyText, std::wstring(buffer));
+
+ password->SelectAll();
+ ::SendMessage(password->GetNativeComponent(), WM_PASTE, 0, 0);
+ ::GetWindowText(password->GetNativeComponent(), buffer, 1024);
+ EXPECT_EQ(kNormalText, std::wstring(buffer));
+
+ // Copy from read_only so the string we are pasting is not the same as the
+ // current one.
+ read_only->SelectAll();
+ ::SendMessage(read_only->GetNativeComponent(), WM_COPY, 0, 0);
+ normal->SelectAll();
+ ::SendMessage(normal->GetNativeComponent(), WM_PASTE, 0, 0);
+ ::GetWindowText(normal->GetNativeComponent(), buffer, 1024);
+ EXPECT_EQ(kReadOnlyText, std::wstring(buffer));
+}
+#endif
+
+#if defined(OS_WIN)
+////////////////////////////////////////////////////////////////////////////////
+// Mouse-wheel message rerouting
+////////////////////////////////////////////////////////////////////////////////
+class ButtonTest : public NativeButton {
+ public:
+ ButtonTest(ButtonListener* listener, const std::wstring& label)
+ : NativeButton(listener, label) {
+ }
+
+ HWND GetHWND() {
+ return static_cast<NativeButtonWin*>(native_wrapper_)->GetHWND();
+ }
+};
+
+class CheckboxTest : public Checkbox {
+ public:
+ explicit CheckboxTest(const std::wstring& label) : Checkbox(label) {
+ }
+
+ HWND GetHWND() {
+ return static_cast<NativeCheckboxWin*>(native_wrapper_)->GetHWND();
+ }
+};
+
+class ScrollableTestView : public View {
+ public:
+ ScrollableTestView() { }
+
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(100, 10000);
+ }
+
+ virtual void Layout() {
+ SizeToPreferredSize();
+ }
+};
+
+class TestViewWithControls : public View {
+ public:
+ TestViewWithControls() {
+ button_ = new ButtonTest(NULL, L"Button");
+ checkbox_ = new CheckboxTest(L"My checkbox");
+ text_field_ = new TextField();
+ AddChildView(button_);
+ AddChildView(checkbox_);
+ AddChildView(text_field_);
+ }
+
+ ButtonTest* button_;
+ CheckboxTest* checkbox_;
+ TextField* text_field_;
+};
+
+class SimpleWindowDelegate : public WindowDelegate {
+ public:
+ SimpleWindowDelegate(View* contents) : contents_(contents) { }
+
+ virtual void DeleteDelegate() { delete this; }
+
+ virtual View* GetContentsView() { return contents_; }
+
+ private:
+ View* contents_;
+};
+
+// Tests that the mouse-wheel messages are correctly rerouted to the window
+// under the mouse.
+// TODO(jcampan): http://crbug.com/10572 Disabled as it fails on the Vista build
+// bot.
+TEST_F(ViewTest, DISABLED_RerouteMouseWheelTest) {
+ TestViewWithControls* view_with_controls = new TestViewWithControls();
+ views::Window* window1 =
+ views::Window::CreateChromeWindow(
+ NULL, gfx::Rect(0, 0, 100, 100),
+ new SimpleWindowDelegate(view_with_controls));
+ window1->Show();
+ ScrollView* scroll_view = new ScrollView();
+ scroll_view->SetContents(new ScrollableTestView());
+ views::Window* window2 =
+ views::Window::CreateChromeWindow(NULL, gfx::Rect(200, 200, 100, 100),
+ new SimpleWindowDelegate(scroll_view));
+ window2->Show();
+ EXPECT_EQ(0, scroll_view->GetVisibleRect().y());
+
+ // Make the window1 active, as this is what it would be in real-world.
+ window1->Activate();
+
+ // Let's send a mouse-wheel message to the different controls and check that
+ // it is rerouted to the window under the mouse (effectively scrolling the
+ // scroll-view).
+
+ // First to the Window's HWND.
+ ::SendMessage(view_with_controls->GetWidget()->GetNativeView(),
+ WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(250, 250));
+ EXPECT_EQ(20, scroll_view->GetVisibleRect().y());
+
+ // Then the button.
+ ::SendMessage(view_with_controls->button_->GetHWND(),
+ WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(250, 250));
+ EXPECT_EQ(40, scroll_view->GetVisibleRect().y());
+
+ // Then the check-box.
+ ::SendMessage(view_with_controls->checkbox_->GetHWND(),
+ WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(250, 250));
+ EXPECT_EQ(60, scroll_view->GetVisibleRect().y());
+
+ // Then the text-field.
+ ::SendMessage(view_with_controls->text_field_->GetNativeComponent(),
+ WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(250, 250));
+ EXPECT_EQ(80, scroll_view->GetVisibleRect().y());
+
+ // Ensure we don't scroll when the mouse is not over that window.
+ ::SendMessage(view_with_controls->text_field_->GetNativeComponent(),
+ WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(50, 50));
+ EXPECT_EQ(80, scroll_view->GetVisibleRect().y());
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Dialogs' default button
+////////////////////////////////////////////////////////////////////////////////
+
+class TestDialogView : public views::View,
+ public views::DialogDelegate,
+ public views::ButtonListener {
+ public:
+ TestDialogView()
+ : last_pressed_button_(NULL),
+ button1_(NULL),
+ button2_(NULL),
+ checkbox_(NULL),
+ canceled_(false),
+ oked_(false) {
+ }
+
+ // views::DialogDelegate implementation:
+ virtual int GetDefaultDialogButton() const {
+ return MessageBoxFlags::DIALOGBUTTON_OK;
+ }
+
+ virtual View* GetContentsView() {
+ views::View* container = new views::View();
+ button1_ = new views::NativeButton(this, L"Button1");
+ button2_ = new views::NativeButton(this, L"Button2");
+ checkbox_ = new views::Checkbox(L"My checkbox");
+ container->AddChildView(button1_);
+ container->AddChildView(button2_);
+ container->AddChildView(checkbox_);
+ return container;
+ }
+
+ // Prevent the dialog from really closing (so we can click the OK/Cancel
+ // buttons to our heart's content).
+ virtual bool Cancel() {
+ canceled_ = true;
+ return false;
+ }
+ virtual bool Accept() {
+ oked_ = true;
+ return false;
+ }
+
+ // views::ButtonListener implementation.
+ virtual void ButtonPressed(Button* sender) {
+ last_pressed_button_ = sender;
+ }
+
+ void ResetStates() {
+ oked_ = false;
+ canceled_ = false;
+ last_pressed_button_ = NULL;
+ }
+
+ views::NativeButton* button1_;
+ views::NativeButton* button2_;
+ views::NativeButton* checkbox_;
+ views::Button* last_pressed_button_;
+
+ bool canceled_;
+ bool oked_;
+};
+
+
+class DefaultButtonTest : public ViewTest {
+ public:
+ enum ButtonID {
+ OK,
+ CANCEL,
+ BUTTON1,
+ BUTTON2
+ };
+
+ DefaultButtonTest()
+ : native_window_(NULL),
+ focus_manager_(NULL),
+ client_view_(NULL),
+ ok_button_(NULL),
+ cancel_button_(NULL) {
+ }
+
+ virtual void SetUp() {
+ dialog_view_ = new TestDialogView();
+ views::Window* window =
+ views::Window::CreateChromeWindow(NULL, gfx::Rect(0, 0, 100, 100),
+ dialog_view_);
+ window->Show();
+ native_window_ = window->GetNativeWindow();
+ focus_manager_ = FocusManager::GetFocusManager(native_window_);
+ client_view_ =
+ static_cast<views::DialogClientView*>(window->GetClientView());
+ ok_button_ = client_view_->ok_button();
+ cancel_button_ = client_view_->cancel_button();
+ }
+
+ void SimularePressingEnterAndCheckDefaultButton(ButtonID button_id) {
+#if defined(OS_WIN)
+ focus_manager_->OnKeyDown(native_window_, WM_KEYDOWN, VK_RETURN, 0);
+#else
+ // TODO(platform)
+ return;
+#endif
+ switch (button_id) {
+ case OK:
+ EXPECT_TRUE(dialog_view_->oked_);
+ EXPECT_FALSE(dialog_view_->canceled_);
+ EXPECT_FALSE(dialog_view_->last_pressed_button_);
+ break;
+ case CANCEL:
+ EXPECT_FALSE(dialog_view_->oked_);
+ EXPECT_TRUE(dialog_view_->canceled_);
+ EXPECT_FALSE(dialog_view_->last_pressed_button_);
+ break;
+ case BUTTON1:
+ EXPECT_FALSE(dialog_view_->oked_);
+ EXPECT_FALSE(dialog_view_->canceled_);
+ EXPECT_TRUE(dialog_view_->last_pressed_button_ ==
+ dialog_view_->button1_);
+ break;
+ case BUTTON2:
+ EXPECT_FALSE(dialog_view_->oked_);
+ EXPECT_FALSE(dialog_view_->canceled_);
+ EXPECT_TRUE(dialog_view_->last_pressed_button_ ==
+ dialog_view_->button2_);
+ break;
+ }
+ dialog_view_->ResetStates();
+ }
+
+ gfx::NativeWindow native_window_;
+ views::FocusManager* focus_manager_;
+ TestDialogView* dialog_view_;
+ DialogClientView* client_view_;
+ views::NativeButton* ok_button_;
+ views::NativeButton* cancel_button_;
+};
+
+TEST_F(DefaultButtonTest, DialogDefaultButtonTest) {
+ // Window has just been shown, we expect the default button specified in the
+ // DialogDelegate.
+ EXPECT_TRUE(ok_button_->is_default());
+
+ // Simulate pressing enter, that should trigger the OK button.
+ SimularePressingEnterAndCheckDefaultButton(OK);
+
+ // Simulate focusing another button, it should become the default button.
+ client_view_->FocusWillChange(ok_button_, dialog_view_->button1_);
+ EXPECT_FALSE(ok_button_->is_default());
+ EXPECT_TRUE(dialog_view_->button1_->is_default());
+ // Simulate pressing enter, that should trigger button1.
+ SimularePressingEnterAndCheckDefaultButton(BUTTON1);
+
+ // Now select something that is not a button, the OK should become the default
+ // button again.
+ client_view_->FocusWillChange(dialog_view_->button1_,
+ dialog_view_->checkbox_);
+ EXPECT_TRUE(ok_button_->is_default());
+ EXPECT_FALSE(dialog_view_->button1_->is_default());
+ SimularePressingEnterAndCheckDefaultButton(OK);
+
+ // Select yet another button.
+ client_view_->FocusWillChange(dialog_view_->checkbox_,
+ dialog_view_->button2_);
+ EXPECT_FALSE(ok_button_->is_default());
+ EXPECT_FALSE(dialog_view_->button1_->is_default());
+ EXPECT_TRUE(dialog_view_->button2_->is_default());
+ SimularePressingEnterAndCheckDefaultButton(BUTTON2);
+
+ // Focus nothing.
+ client_view_->FocusWillChange(dialog_view_->button2_, NULL);
+ EXPECT_TRUE(ok_button_->is_default());
+ EXPECT_FALSE(dialog_view_->button1_->is_default());
+ EXPECT_FALSE(dialog_view_->button2_->is_default());
+ SimularePressingEnterAndCheckDefaultButton(OK);
+
+ // Focus the cancel button.
+ client_view_->FocusWillChange(NULL, cancel_button_);
+ EXPECT_FALSE(ok_button_->is_default());
+ EXPECT_TRUE(cancel_button_->is_default());
+ EXPECT_FALSE(dialog_view_->button1_->is_default());
+ EXPECT_FALSE(dialog_view_->button2_->is_default());
+ SimularePressingEnterAndCheckDefaultButton(CANCEL);
+}
diff --git a/views/view_win.cc b/views/view_win.cc
new file mode 100644
index 0000000..6dfe183
--- /dev/null
+++ b/views/view_win.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/view.h"
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/path.h"
+#include "app/os_exchange_data.h"
+#include "base/scoped_handle.h"
+#include "base/string_util.h"
+#include "views/accessibility/view_accessibility_wrapper.h"
+#include "views/border.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+FocusManager* View::GetFocusManager() {
+ Widget* widget = GetWidget();
+ if (!widget)
+ return NULL;
+
+ HWND hwnd = widget->GetNativeView();
+ if (!hwnd)
+ return NULL;
+
+ return FocusManager::GetFocusManager(hwnd);
+}
+
+void View::DoDrag(const MouseEvent& e, int press_x, int press_y) {
+ int drag_operations = GetDragOperations(press_x, press_y);
+ if (drag_operations == DragDropTypes::DRAG_NONE)
+ return;
+
+ 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 calling us back.
+ RootView* root_view = GetRootView();
+ root_view->StartDragForViewFromMouseEvent(this, data, drag_operations);
+}
+
+ViewAccessibilityWrapper* View::GetViewAccessibilityWrapper() {
+ if (accessibility_.get() == NULL) {
+ accessibility_.reset(new ViewAccessibilityWrapper(this));
+ }
+ return accessibility_.get();
+}
+
+bool View::HitTest(const gfx::Point& l) const {
+ if (l.x() >= 0 && l.x() < static_cast<int>(width()) &&
+ l.y() >= 0 && l.y() < static_cast<int>(height())) {
+ if (HasHitTestMask()) {
+ gfx::Path mask;
+ GetHitTestMask(&mask);
+ ScopedHRGN rgn(mask.CreateHRGN());
+ return !!PtInRegion(rgn, l.x(), l.y());
+ }
+ // No mask, but inside our bounds.
+ return true;
+ }
+ // Outside our bounds.
+ return false;
+}
+
+HCURSOR View::GetCursorForPoint(Event::EventType event_type, int x, int y) {
+ return NULL;
+}
+
+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()->GetWidget()->GetNativeView());
+}
+
+int View::GetHorizontalDragThreshold() {
+ static int threshold = -1;
+ if (threshold == -1)
+ threshold = GetSystemMetrics(SM_CXDRAG) / 2;
+ return threshold;
+}
+
+int View::GetVerticalDragThreshold() {
+ static int threshold = -1;
+ if (threshold == -1)
+ threshold = GetSystemMetrics(SM_CYDRAG) / 2;
+ return threshold;
+}
+
+} // namespace views
diff --git a/views/views.vcproj b/views/views.vcproj
new file mode 100644
index 0000000..c10e68d
--- /dev/null
+++ b/views/views.vcproj
@@ -0,0 +1,869 @@
+<?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;$(SolutionDir)\tools\build\win\precompiled_wtl.vsprops;..\third_party\icu38\build\using_icu.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;..\third_party\icu38\build\using_icu.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\view_accessibility.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\accessibility\view_accessibility.h"
+ >
+ </File>
+ <File
+ RelativePath=".\accessibility\view_accessibility_wrapper.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\accessibility\view_accessibility_wrapper.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="widget"
+ >
+ <File
+ RelativePath=".\widget\accelerator_handler.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\accelerator_handler.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\aero_tooltip_manager.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\aero_tooltip_manager.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\root_view.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\root_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\root_view_drop_target.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\root_view_drop_target.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\root_view_win.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\tooltip_manager.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\tooltip_manager.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\widget.h"
+ >
+ </File>
+ <File
+ RelativePath=".\widget\widget_win.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)\$(InputName)1.obj"
+ XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\widget\widget_win.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="window"
+ >
+ <File
+ RelativePath=".\window\client_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\client_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\custom_frame_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\custom_frame_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\dialog_client_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\dialog_client_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\dialog_delegate.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\dialog_delegate.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\native_frame_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\native_frame_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\non_client_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\non_client_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window_delegate.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window_delegate.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window_resources.h"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\window\window_win.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="focus"
+ >
+ <File
+ RelativePath=".\focus\external_focus_tracker.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\external_focus_tracker.h"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\focus_manager.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\focus_manager.h"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\focus_util_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\focus_util_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\view_storage.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\focus\view_storage.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="controls"
+ >
+ <File
+ RelativePath=".\controls\combo_box.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\combo_box.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\hwnd_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\hwnd_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\image_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\image_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\label.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\label.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\link.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\link.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\message_box_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\message_box_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_control.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_control.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_control_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_control_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_view_host.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\native_view_host.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scroll_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scroll_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\separator.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\separator.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\single_split_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\single_split_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tabbed_pane.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tabbed_pane.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\text_field.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\text_field.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\throbber.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\throbber.h"
+ >
+ </File>
+ <Filter
+ Name="button"
+ >
+ <File
+ RelativePath=".\controls\button\button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\button_dropdown.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\button_dropdown.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\checkbox.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\checkbox.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\custom_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\custom_button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\image_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\image_button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\menu_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\menu_button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\native_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\native_button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\native_button_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\native_button_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\native_button_wrapper.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\radio_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\radio_button.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\text_button.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\button\text_button.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="scrollbar"
+ >
+ <File
+ RelativePath=".\controls\scrollbar\bitmap_scroll_bar.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scrollbar\bitmap_scroll_bar.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scrollbar\native_scroll_bar.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scrollbar\native_scroll_bar.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scrollbar\scroll_bar.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\scrollbar\scroll_bar.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tree"
+ >
+ <File
+ RelativePath=".\controls\tree\tree_model.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tree\tree_node_iterator.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tree\tree_node_model.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tree\tree_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\tree\tree_view.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="table"
+ >
+ <File
+ RelativePath=".\controls\table\group_table_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\table\group_table_view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\table\table_view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\table\table_view.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="menu"
+ >
+ <File
+ RelativePath=".\controls\menu\chrome_menu.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\menu\chrome_menu.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\menu\controller.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\menu\menu.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\menu\menu.h"
+ >
+ </File>
+ <File
+ RelativePath=".\controls\menu\view_menu_delegate.h"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <File
+ RelativePath=".\accelerator.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\accelerator.h"
+ >
+ </File>
+ <File
+ RelativePath=".\background.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\background.h"
+ >
+ </File>
+ <File
+ RelativePath=".\border.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\border.h"
+ >
+ </File>
+ <File
+ RelativePath=".\event.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\event.h"
+ >
+ </File>
+ <File
+ RelativePath=".\event_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\fill_layout.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\fill_layout.h"
+ >
+ </File>
+ <File
+ RelativePath=".\grid_layout.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\grid_layout.h"
+ >
+ </File>
+ <File
+ RelativePath=".\layout_manager.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\layout_manager.h"
+ >
+ </File>
+ <File
+ RelativePath=".\painter.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\painter.h"
+ >
+ </File>
+ <File
+ RelativePath="$(SolutionDir)\tools\build\win\precompiled_wtl.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="$(SolutionDir)\tools\build\win\precompiled_wtl.h"
+ >
+ </File>
+ <File
+ RelativePath=".\repeat_controller.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\repeat_controller.h"
+ >
+ </File>
+ <File
+ RelativePath=".\view.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\view.h"
+ >
+ </File>
+ <File
+ RelativePath=".\view_constants.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\view_constants.h"
+ >
+ </File>
+ <File
+ RelativePath=".\view_win.cc"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/views/views.vsprops b/views/views.vsprops
new file mode 100644
index 0000000..72a6e0d
--- /dev/null
+++ b/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;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories="..\..\;&quot;$(IntDir)\..\generated_resources\&quot;"
+ />
+</VisualStudioPropertySheet>
diff --git a/views/widget/accelerator_handler.cc b/views/widget/accelerator_handler.cc
new file mode 100644
index 0000000..386357f
--- /dev/null
+++ b/views/widget/accelerator_handler.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/accelerator_handler.h"
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {
+}
+
+bool AcceleratorHandler::Dispatch(const MSG& msg) {
+ bool process_message = true;
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) {
+ FocusManager* focus_manager = FocusManager::GetFocusManager(msg.hwnd);
+ if (focus_manager) {
+ // FocusManager::OnKeyDown and OnKeyUp return false if this message has
+ // been consumed and should not be propagated further.
+ switch (msg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ process_message = focus_manager->OnKeyDown(msg.hwnd, msg.message,
+ msg.wParam, msg.lParam);
+ break;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ process_message = focus_manager->OnKeyUp(msg.hwnd, msg.message,
+ msg.wParam, msg.lParam);
+ break;
+ }
+ }
+ }
+
+ if (process_message) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return true;
+}
+
+} // namespace views
diff --git a/views/widget/accelerator_handler.h b/views/widget/accelerator_handler.h
new file mode 100644
index 0000000..5ee896c
--- /dev/null
+++ b/views/widget/accelerator_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_ACCELERATOR_HANDLER_H_
+#define VIEWS_WIDGET_ACCELERATOR_HANDLER_H_
+
+#include "base/message_loop.h"
+
+namespace views {
+
+// 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 MessageLoopForUI::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);
+};
+
+} // namespace views
+
+#endif // VIEWS_WIDGET_ACCELERATOR_HANDLER_H_
diff --git a/views/widget/aero_tooltip_manager.cc b/views/widget/aero_tooltip_manager.cc
new file mode 100644
index 0000000..ca58c24
--- /dev/null
+++ b/views/widget/aero_tooltip_manager.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/aero_tooltip_manager.h"
+
+#include <windows.h>
+#include <atlbase.h>
+#include <atlapp.h> // for GET_X/Y_LPARAM
+#include <commctrl.h>
+#include <shlobj.h>
+
+#include "app/l10n_util_win.h"
+#include "base/message_loop.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// AeroTooltipManager, public:
+
+AeroTooltipManager::AeroTooltipManager(Widget* widget, HWND parent)
+ : TooltipManager(widget, 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;
+ HideKeyboardTooltip();
+ 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;
+ }
+}
+
+void AeroTooltipManager::OnMouseLeave() {
+ last_mouse_x_ = last_mouse_y_ = -1;
+ UpdateTooltip();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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);
+
+ l10n_util::AdjustUIFontForWindow(tooltip_hwnd_);
+
+ // 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 views
diff --git a/views/widget/aero_tooltip_manager.h b/views/widget/aero_tooltip_manager.h
new file mode 100644
index 0000000..fe5856d
--- /dev/null
+++ b/views/widget/aero_tooltip_manager.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_
+#define VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_
+
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "views/widget/tooltip_manager.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 TooltipManager {
+ public:
+ AeroTooltipManager(Widget* widget, HWND parent);
+ virtual ~AeroTooltipManager();
+
+ virtual void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param);
+ virtual void OnMouseLeave();
+
+ 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 views
+
+#endif // #ifndef VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_
diff --git a/views/widget/root_view.cc b/views/widget/root_view.cc
new file mode 100644
index 0000000..fd9f1c0
--- /dev/null
+++ b/views/widget/root_view.cc
@@ -0,0 +1,1001 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/root_view.h"
+
+#include <algorithm>
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_canvas.h"
+#if defined(OS_WIN)
+#include "base/base_drag_source.h"
+#endif
+#include "base/logging.h"
+#include "base/message_loop.h"
+#if defined(OS_WIN)
+#include "views/focus/view_storage.h"
+#include "views/widget/root_view_drop_target.h"
+#endif
+#include "views/widget/widget.h"
+#include "views/window/window.h"
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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_->PaintNow();
+ }
+ private:
+ // The target root view.
+ RootView* root_view_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(PaintTask);
+};
+
+const char RootView::kViewClassName[] = "views/RootView";
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RootView - constructors, destructors, initialization
+//
+/////////////////////////////////////////////////////////////////////////////
+
+RootView::RootView(Widget* widget)
+ : mouse_pressed_handler_(NULL),
+ mouse_move_handler_(NULL),
+ last_click_handler_(NULL),
+ widget_(widget),
+ invalid_rect_urgent_(false),
+ pending_paint_task_(NULL),
+ paint_task_needed_(false),
+ explicit_mouse_handler_(false),
+#if defined(OS_WIN)
+ previous_cursor_(NULL),
+#endif
+ default_keyboard_handler_(NULL),
+ focus_listener_(NULL),
+ focus_on_mouse_pressed_(false),
+ ignore_set_focus_calls_(false),
+ focus_traversable_parent_(NULL),
+ focus_traversable_parent_view_(NULL),
+ 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 gfx::Rect& 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_.IsEmpty())
+ invalid_rect_ = r;
+ else
+ invalid_rect_ = invalid_rect_.Union(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();
+}
+
+#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_.IsEmpty())
+ 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 clip_x = invalid_rect_.x() + x();
+ int clip_y = invalid_rect_.y() + y();
+ canvas->ClipRectInt(clip_x, clip_y, invalid_rect_.width(),
+ invalid_rect_.height());
+
+ // Paint the tree
+ View::ProcessPaint(canvas);
+
+ // Restore the previous transform
+ canvas->restore();
+
+ ClearPaintRect();
+}
+
+void RootView::PaintNow() {
+ if (pending_paint_task_) {
+ pending_paint_task_->Cancel();
+ pending_paint_task_ = NULL;
+ }
+ if (!paint_task_needed_)
+ return;
+ Widget* widget = GetWidget();
+ if (widget)
+ widget->PaintNow(invalid_rect_);
+}
+
+bool RootView::NeedsPainting(bool urgent) {
+ bool has_invalid_rect = !invalid_rect_.IsEmpty();
+ if (urgent) {
+ if (invalid_rect_urgent_)
+ return has_invalid_rect;
+ else
+ return false;
+ } else {
+ return has_invalid_rect;
+ }
+}
+
+const gfx::Rect& RootView::GetScheduledPaintRect() {
+ return invalid_rect_;
+}
+
+gfx::Rect RootView::GetScheduledPaintRectConstrainedToSize() {
+ if (invalid_rect_.IsEmpty())
+ return invalid_rect_;
+
+ return invalid_rect_.Intersect(GetLocalBounds(true));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RootView - tree
+//
+/////////////////////////////////////////////////////////////////////////////
+
+Widget* RootView::GetWidget() const {
+ return widget_;
+}
+
+void RootView::ThemeChanged() {
+ View::ThemeChanged();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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 defined(OS_WIN)
+ if (drop_target_.get())
+ drop_target_->ResetTargetViewIfEquals(child);
+#else
+ NOTIMPLEMENTED();
+#endif
+
+ if (mouse_move_handler_ == child) {
+ mouse_move_handler_ = NULL;
+ }
+
+ if (GetFocusedView() == child) {
+ FocusView(NULL);
+ }
+
+ if (child == drag_view_)
+ drag_view_ = NULL;
+
+ if (default_keyboard_handler_ == child) {
+ default_keyboard_handler_ = NULL;
+ }
+
+ // For a given widget hierarchy, focus is tracked by a FocusManager attached
+ // to our nearest enclosing Window. <-- Important Assumption!
+ // We may not have access to our window if this function is called as a
+ // result of teardown during the deletion of the RootView and its hierarchy,
+ // so we don't bother notifying the FocusManager in that case because it
+ // will have already been destroyed (the Widget that contains us is
+ // NCDESTROY'ed which in turn destroys the focus manager _before_ the
+ // RootView is deleted.)
+#if defined(OS_WIN)
+ Window* window = GetWindow();
+ if (window) {
+ FocusManager* focus_manager =
+ FocusManager::GetFocusManager(window->GetNativeWindow());
+ focus_manager->ViewRemoved(parent, child);
+ }
+ ViewStorage::GetSharedInstance()->ViewRemoved(parent, child);
+#endif
+ }
+}
+
+void RootView::SetFocusOnMousePressed(bool f) {
+ focus_on_mouse_pressed_ = f;
+}
+
+bool RootView::OnMousePressed(const MouseEvent& e) {
+ // This function does not normally handle non-client messages except for
+ // non-client double-clicks. Actually, all double-clicks are special as the
+ // are formed from a single-click followed by a double-click event. When the
+ // double-click event lands on a different view than its single-click part,
+ // we transform it into a single-click which prevents odd things.
+ if ((e.GetFlags() & MouseEvent::EF_IS_NON_CLIENT) &&
+ !(e.GetFlags() & MouseEvent::EF_IS_DOUBLE_CLICK)) {
+ last_click_handler_ = NULL;
+ return false;
+ }
+
+ 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.location());
+ 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.
+ MouseEvent mouse_pressed_event(e, this, mouse_pressed_handler_);
+
+ // Remove the double-click flag if the handler is different than the
+ // one which got the first click part of the double-click.
+ if (mouse_pressed_handler_ != last_click_handler_)
+ mouse_pressed_event.set_flags(e.GetFlags() &
+ ~MouseEvent::EF_IS_DOUBLE_CLICK);
+
+ drag_info.Reset();
+ 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) {
+ last_click_handler_ = mouse_pressed_handler_;
+ return true;
+ }
+ }
+
+ // Reset mouse_pressed_handler_ to indicate that no processing is occurring.
+ mouse_pressed_handler_ = NULL;
+
+ if (focus_on_mouse_pressed_) {
+#if defined(OS_WIN)
+ HWND hwnd = GetWidget()->GetNativeView();
+ if (::GetFocus() != hwnd) {
+ ::SetFocus(hwnd);
+ }
+#else
+ NOTIMPLEMENTED();
+#endif
+ }
+
+ // In the event that a double-click is not handled after traversing the
+ // entire hierarchy (even as a single-click when sent to a different view),
+ // it must be marked as handled to avoid anything happening from default
+ // processing if it the first click-part was handled by us.
+ if (last_click_handler_ && e.GetFlags() & MouseEvent::EF_IS_DOUBLE_CLICK)
+ hit_disabled_view = true;
+
+ last_click_handler_ = NULL;
+ return hit_disabled_view;
+}
+
+bool RootView::ConvertPointToMouseHandler(const gfx::Point& l,
+ gfx::Point* 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_->GetWidget()) {
+ *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);
+
+ gfx::Point p;
+ ConvertPointToMouseHandler(e.location(), &p);
+ MouseEvent mouse_event(e.GetType(), p.x(), p.y(), e.GetFlags());
+ return mouse_pressed_handler_->ProcessMouseDragged(mouse_event, &drag_info);
+ }
+ return false;
+}
+
+void RootView::OnMouseReleased(const MouseEvent& e, bool canceled) {
+ UpdateCursor(e);
+
+ if (mouse_pressed_handler_) {
+ gfx::Point p;
+ ConvertPointToMouseHandler(e.location(), &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::OnMouseMoved(const MouseEvent& e) {
+ View* v = GetViewForPoint(e.location());
+ // 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);
+
+#if defined(OS_WIN)
+ HCURSOR cursor = mouse_move_handler_->GetCursorForPoint(
+ moved_event.GetType(), moved_event.x(), moved_event.y());
+ if (cursor) {
+ previous_cursor_ = ::SetCursor(cursor);
+ } else if (previous_cursor_) {
+ ::SetCursor(previous_cursor_);
+ previous_cursor_ = NULL;
+ }
+#else
+ NOTIMPLEMENTED();
+#endif
+ } else if (mouse_move_handler_ != NULL) {
+ MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0);
+ mouse_move_handler_->OnMouseExited(exited_event);
+#if defined(OS_WIN)
+ if (previous_cursor_) {
+ ::SetCursor(previous_cursor_);
+ previous_cursor_ = NULL;
+ }
+#else
+ NOTIMPLEMENTED();
+#endif
+ }
+}
+
+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::OnWidgetCreated() {
+#if defined(OS_WIN)
+ DCHECK(!drop_target_.get());
+ drop_target_ = new RootViewDropTarget(this);
+#else
+ // TODO(port): Port RootViewDropTarget and this goes away.
+ NOTIMPLEMENTED();
+#endif
+}
+
+void RootView::OnWidgetDestroyed() {
+#if defined(OS_WIN)
+ if (drop_target_.get()) {
+ RevokeDragDrop(GetWidget()->GetNativeView());
+ drop_target_ = NULL;
+ }
+#else
+ // TODO(port): Port RootViewDropTarget and this goes away.
+ NOTIMPLEMENTED();
+#endif
+ widget_ = 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()) {
+#if defined(OS_WIN)
+ FocusManager* focus_manager = GetFocusManager();
+ DCHECK(focus_manager) << "No Focus Manager for Window " <<
+ (GetWidget() ? GetWidget()->GetNativeView() : 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);
+#else
+ // TODO(port): Port the focus manager and this goes away.
+ NOTIMPLEMENTED();
+#endif
+ }
+}
+
+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);
+}
+
+bool RootView::ProcessKeyEvent(const KeyEvent& event) {
+ bool consumed = false;
+
+ View* v = GetFocusedView();
+#if defined(OS_WIN)
+ // Special case to handle right-click context menus triggered by the
+ // keyboard.
+ if (v && v->IsEnabled() && ((event.GetCharacter() == VK_APPS) ||
+ (event.GetCharacter() == VK_F10 && event.IsShiftDown()))) {
+ gfx::Point screen_loc = v->GetKeyboardContextMenuLocation();
+ v->ShowContextMenu(screen_loc.x(), screen_loc.y(), false);
+ return true;
+ }
+#else
+ // TODO(port): The above block needs the VK_* refactored out.
+ NOTIMPLEMENTED();
+#endif
+
+ for (; v && v != this && !consumed; v = v->GetParent()) {
+ consumed = (event.GetType() == Event::ET_KEY_PRESSED) ?
+ v->OnKeyPressed(event) : v->OnKeyReleased(event);
+ }
+
+ if (!consumed && default_keyboard_handler_) {
+ consumed = (event.GetType() == Event::ET_KEY_PRESSED) ?
+ default_keyboard_handler_->OnKeyPressed(event) :
+ default_keyboard_handler_->OnKeyReleased(event);
+ }
+
+ return consumed;
+}
+
+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_handler_) {
+ consumed = default_keyboard_handler_->OnMouseWheel(e);
+ }
+ return consumed;
+}
+
+void RootView::SetDefaultKeyboardHandler(View* v) {
+ default_keyboard_handler_ = 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.x();
+ last_mouse_event_y_ = e.y();
+}
+
+std::string RootView::GetClassName() const {
+ return kViewClassName;
+}
+
+void RootView::ClearPaintRect() {
+ invalid_rect_.SetRect(0, 0, 0, 0);
+
+ // 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;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RootView - accessibility
+//
+/////////////////////////////////////////////////////////////////////////////
+
+bool RootView::GetAccessibleRole(AccessibilityTypes::Role* role) {
+ DCHECK(role);
+
+ *role = AccessibilityTypes::ROLE_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);
+}
+
+View* RootView::GetDragView() {
+ return drag_view_;
+}
+
+} // namespace views
diff --git a/views/widget/root_view.h b/views/widget/root_view.h
new file mode 100644
index 0000000..d775ac1
--- /dev/null
+++ b/views/widget/root_view.h
@@ -0,0 +1,363 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_ROOT_VIEW_H_
+#define VIEWS_WIDGET_ROOT_VIEW_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_LINUX)
+#include <gtk/gtk.h>
+#endif
+
+#if defined(OS_WIN)
+#include "base/ref_counted.h"
+#endif
+
+#include "views/focus/focus_manager.h"
+#include "views/view.h"
+
+namespace views {
+
+class PaintTask;
+class RootViewDropTarget;
+class Widget;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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. A RootView is always the
+// first and only child of a Widget.
+//
+// The RootView manages the View hierarchy's interface with the Widget
+// and also maintains the current invalid rect - the region that needs
+// repainting.
+//
+/////////////////////////////////////////////////////////////////////////////
+class RootView : public View,
+ public FocusTraversable {
+ public:
+ static const char kViewClassName[];
+
+ explicit RootView(Widget* widget);
+
+ virtual ~RootView();
+
+ // Layout and Painting functions
+
+ // Overridden from View to implement paint scheduling.
+ virtual void SchedulePaint(const gfx::Rect& 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);
+
+ // If the invalid rect is non-empty and there is a pending paint the RootView
+ // is painted immediately. This is internally invoked as the result of
+ // invoking SchedulePaint.
+ 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 Widget to discover what rectangle should be painted.
+ const gfx::Rect& GetScheduledPaintRect();
+
+ // Returns the region scheduled to paint clipped to the RootViews bounds.
+ gfx::Rect GetScheduledPaintRectConstrainedToSize();
+
+ // Tree functions
+
+ // Get the Widget that hosts this View.
+ virtual Widget* GetWidget() const;
+
+ // Public API for broadcasting theme change notifications to this View
+ // hierarchy.
+ virtual void ThemeChanged();
+
+ // 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 Widget has been fully initialized.
+ // At the time the constructor is invoked the Widget may not be completely
+ // initialized, when this method is invoked, it is.
+ void OnWidgetCreated();
+
+ // Invoked prior to the Widget being destroyed.
+ void OnWidgetDestroyed();
+
+ // Invoked By the Widget if the mouse drag is interrupted by
+ // the system. Invokes OnMouseReleased with a value of true for canceled.
+ void ProcessMouseDragCanceled();
+
+ // Invoked by the Widget instance when the mouse moves outside of the Widget
+ // bounds.
+ virtual void ProcessOnMouseExited();
+
+ // Make the provided view focused. Also make sure that our Widget 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, and finally to the default keyboard handler, until someone consumes
+ // it. Returns whether anyone consumed the event.
+ bool 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: 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 Widgets.
+ void ClearPaintRect();
+
+#if defined(OS_WIN)
+ // Invoked from the Widget to service a WM_PAINT call.
+ void OnPaint(HWND hwnd);
+#elif defined(OS_LINUX)
+ void OnPaint(GdkEventExpose* event);
+#endif
+
+ // Accessibility accessors/mutators, overridden from View.
+ virtual bool GetAccessibleRole(AccessibilityTypes::Role* role);
+ virtual bool GetAccessibleName(std::wstring* name);
+ virtual 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 Widget. In that case, the
+ // conversion cannot take place and *p is unchanged
+ bool ConvertPointToMouseHandler(const gfx::Point& l, gfx::Point *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);
+
+ // 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);
+
+#if defined(OS_WIN)
+ // 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);
+#endif
+
+ // If a view is dragging, this returns it. Otherwise returns NULL.
+ View* GetDragView();
+
+ // The view currently handing down - drag - up
+ View* mouse_pressed_handler_;
+
+ // The view currently handling enter / exit
+ View* mouse_move_handler_;
+
+ // The last view to handle a mouse click, so that we can determine if
+ // a double-click lands on the same view as its single-click part.
+ View* last_click_handler_;
+
+ // The host Widget
+ Widget* widget_;
+
+ // The rectangle that should be painted
+ gfx::Rect 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_;
+
+#if defined(OS_WIN)
+ // Previous cursor
+ HCURSOR previous_cursor_;
+#endif
+
+ // Default keyboard handler
+ View* default_keyboard_handler_;
+
+ // 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_;
+
+#if defined(OS_WIN)
+ // Handles dnd for us.
+ scoped_refptr<RootViewDropTarget> drop_target_;
+#endif
+
+ // 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
+};
+
+} // namespace views
+
+#endif // VIEWS_WIDGET_ROOT_VIEW_H_
diff --git a/views/widget/root_view_drop_target.cc b/views/widget/root_view_drop_target.cc
new file mode 100644
index 0000000..9c1f087
--- /dev/null
+++ b/views/widget/root_view_drop_target.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/root_view_drop_target.h"
+
+#include "app/drag_drop_types.h"
+#include "base/gfx/point.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+RootViewDropTarget::RootViewDropTarget(RootView* root_view)
+ : BaseDropTarget(root_view->GetWidget()->GetNativeView()),
+ 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);
+ gfx::Point 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_) {
+ gfx::Point target_view_location(root_view_location);
+ 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_) {
+ gfx::Point target_view_location(root_view_location);
+ 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) {
+ gfx::Point 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 gfx::Point& 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 views
diff --git a/views/widget/root_view_drop_target.h b/views/widget/root_view_drop_target.h
new file mode 100644
index 0000000..a3c3afd
--- /dev/null
+++ b/views/widget/root_view_drop_target.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_
+#define VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlmisc.h>
+
+#include "app/os_exchange_data.h"
+#include "base/base_drop_target.h"
+
+namespace gfx {
+class Point;
+}
+
+namespace views {
+
+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 gfx::Point& 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 views
+
+#endif // VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_
diff --git a/views/widget/root_view_gtk.cc b/views/widget/root_view_gtk.cc
new file mode 100644
index 0000000..428c695
--- /dev/null
+++ b/views/widget/root_view_gtk.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/root_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/logging.h"
+#include "skia/include/SkColor.h"
+
+namespace views {
+
+void RootView::UpdateCursor(const MouseEvent& e) {
+ NOTIMPLEMENTED();
+}
+
+void RootView::OnPaint(GdkEventExpose* event) {
+ ChromeCanvasPaint canvas(event);
+
+ if (!canvas.isEmpty()) {
+ SchedulePaint(gfx::Rect(canvas.rectangle()), false);
+ if (NeedsPainting(false)) {
+ ProcessPaint(&canvas);
+ }
+ }
+}
+
+}
diff --git a/views/widget/root_view_win.cc b/views/widget/root_view_win.cc
new file mode 100644
index 0000000..ac4a50d
--- /dev/null
+++ b/views/widget/root_view_win.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/root_view.h"
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_canvas.h"
+#include "base/base_drag_source.h"
+#include "base/logging.h"
+#include "views/widget/root_view_drop_target.h"
+
+namespace views {
+
+void RootView::UpdateCursor(const MouseEvent& e) {
+ View *v = GetViewForPoint(e.location());
+
+ if (v && v != this) {
+ gfx::Point l(e.location());
+ 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::OnPaint(HWND hwnd) {
+ gfx::Rect original_dirty_region = GetScheduledPaintRectConstrainedToSize();
+ if (!original_dirty_region.IsEmpty()) {
+ // 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.
+ RECT win_version = original_dirty_region.ToRECT();
+ InvalidateRect(hwnd, &win_version, FALSE);
+ }
+ ChromeCanvasPaint canvas(hwnd);
+ if (!canvas.isEmpty()) {
+ const PAINTSTRUCT& ps = canvas.paintStruct();
+ SchedulePaint(gfx::Rect(ps.rcPaint), false);
+ if (NeedsPainting(false))
+ ProcessPaint(&canvas);
+ }
+}
+
+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();
+ }
+}
+
+}
diff --git a/views/widget/tooltip_manager.cc b/views/widget/tooltip_manager.cc
new file mode 100644
index 0000000..18faf4a
--- /dev/null
+++ b/views/widget/tooltip_manager.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/tooltip_manager.h"
+
+#include <limits>
+
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/common/gfx/text_elider.h"
+#include "chrome/common/win_util.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+//static
+int TooltipManager::tooltip_height_ = 0;
+
+// Default timeout for the tooltip displayed using keyboard.
+// Timeout is mentioned in milliseconds.
+static const int kDefaultTimeout = 4000;
+
+// Maximum number of lines we allow in the tooltip.
+static const int kMaxLines = 6;
+
+// Maximum number of characters we allow in a tooltip.
+static const int kMaxTooltipLength = 1024;
+
+// 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(Widget* widget, HWND parent)
+ : widget_(widget),
+ 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),
+ keyboard_tooltip_hwnd_(NULL),
+#pragma warning(suppress: 4355)
+ keyboard_tooltip_factory_(this) {
+ DCHECK(widget && parent);
+ Init();
+}
+
+TooltipManager::~TooltipManager() {
+ if (tooltip_hwnd_)
+ DestroyWindow(tooltip_hwnd_);
+ if (keyboard_tooltip_hwnd_)
+ DestroyWindow(keyboard_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);
+
+ l10n_util::AdjustUIFontForWindow(tooltip_hwnd_);
+
+ // 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_ && keyboard_tooltip_hwnd_ == NULL) {
+ 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 = widget_->GetRootView();
+ last_tooltip_view_ = root_view->GetViewForPoint(
+ gfx::Point(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.
+ gfx::Point view_loc(last_mouse_x_, last_mouse_y_);
+ View::ConvertPointToView(widget_->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_,
+ last_mouse_x_, last_mouse_y_, tooltip_hwnd_);
+ // Adjust the clipped tooltip text for locale direction.
+ l10n_util::AdjustStringForLocaleDirection(clipped_text_,
+ &clipped_text_);
+ 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
+ gfx::Point text_origin;
+ if (tooltip_height_ == 0)
+ tooltip_height_ = CalcTooltipHeight();
+ gfx::Point view_loc(last_mouse_x_, last_mouse_y_);
+ View::ConvertPointToView(widget_->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.
+ gfx::Point view_loc;
+ 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,
+ int position_x,
+ int position_y,
+ HWND window) {
+ *max_width = 0;
+ *line_count = 0;
+
+ // Clamp the tooltip length to kMaxTooltipLength so that we don't
+ // accidentally DOS the user with a mega tooltip (since Windows doesn't seem
+ // to do this itself).
+ if (text->length() > kMaxTooltipLength)
+ *text = text->substr(0, kMaxTooltipLength);
+
+ // Determine the available width for the tooltip.
+ gfx::Point screen_loc(position_x, position_y);
+ View::ConvertPointToScreen(widget_->GetRootView(), &screen_loc);
+ gfx::Rect monitor_bounds =
+ win_util::GetMonitorBoundsForRect(gfx::Rect(screen_loc.x(),
+ screen_loc.y(),
+ 0, 0));
+ RECT tooltip_margin;
+ SendMessage(window, 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 = widget_->GetRootView();
+ View* view = root_view->GetViewForPoint(gfx::Point(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.
+ gfx::Point 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_NCMOUSEMOVE && u_msg <= WM_NCXBUTTONDBLCLK) {
+ // NC message coordinates are in screen coordinates.
+ gfx::Rect frame_bounds;
+ widget_->GetBounds(&frame_bounds, true);
+ x -= frame_bounds.x();
+ y -= frame_bounds.y();
+ }
+
+ if (u_msg != WM_MOUSEMOVE || last_mouse_x_ != x || last_mouse_y_ != y) {
+ last_mouse_x_ = x;
+ last_mouse_y_ = y;
+ HideKeyboardTooltip();
+ 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);
+}
+
+void TooltipManager::ShowKeyboardTooltip(View* focused_view) {
+ if (tooltip_showing_) {
+ SendMessage(tooltip_hwnd_, TTM_POP, 0, 0);
+ tooltip_text_.clear();
+ }
+ HideKeyboardTooltip();
+ std::wstring tooltip_text;
+ if (!focused_view->GetTooltipText(0, 0, &tooltip_text))
+ return;
+ gfx::Rect focused_bounds = focused_view->bounds();
+ gfx::Point screen_point;
+ focused_view->ConvertPointToScreen(focused_view, &screen_point);
+ gfx::Point relative_point_coordinates;
+ focused_view->ConvertPointToWidget(focused_view, &relative_point_coordinates);
+ keyboard_tooltip_hwnd_ = CreateWindowEx(
+ WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(),
+ TOOLTIPS_CLASS, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
+ SendMessage(keyboard_tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0,
+ std::numeric_limits<short>::max());
+ int tooltip_width;
+ int line_count;
+ TrimTooltipToFit(&tooltip_text, &tooltip_width, &line_count,
+ relative_point_coordinates.x(),
+ relative_point_coordinates.y(), keyboard_tooltip_hwnd_);
+ TOOLINFO keyboard_toolinfo;
+ memset(&keyboard_toolinfo, 0, sizeof(keyboard_toolinfo));
+ keyboard_toolinfo.cbSize = sizeof(keyboard_toolinfo);
+ keyboard_toolinfo.hwnd = parent_;
+ keyboard_toolinfo.uFlags = TTF_TRACK | TTF_TRANSPARENT | TTF_IDISHWND ;
+ keyboard_toolinfo.lpszText = const_cast<WCHAR*>(tooltip_text.c_str());
+ SendMessage(keyboard_tooltip_hwnd_, TTM_ADDTOOL, 0,
+ reinterpret_cast<LPARAM>(&keyboard_toolinfo));
+ SendMessage(keyboard_tooltip_hwnd_, TTM_TRACKACTIVATE, TRUE,
+ reinterpret_cast<LPARAM>(&keyboard_toolinfo));
+ if (!tooltip_height_)
+ tooltip_height_ = CalcTooltipHeight();
+ RECT rect_bounds = {screen_point.x(),
+ screen_point.y() + focused_bounds.height(),
+ screen_point.x() + tooltip_width,
+ screen_point.y() + focused_bounds.height() +
+ line_count * tooltip_height_ };
+ gfx::Rect monitor_bounds =
+ win_util::GetMonitorBoundsForRect(gfx::Rect(rect_bounds));
+ rect_bounds = gfx::Rect(rect_bounds).AdjustToFit(monitor_bounds).ToRECT();
+ ::SetWindowPos(keyboard_tooltip_hwnd_, NULL, rect_bounds.left,
+ rect_bounds.top, 0, 0,
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ keyboard_tooltip_factory_.NewRunnableMethod(
+ &TooltipManager::DestroyKeyboardTooltipWindow, keyboard_tooltip_hwnd_),
+ kDefaultTimeout);
+}
+
+void TooltipManager::HideKeyboardTooltip() {
+ if (keyboard_tooltip_hwnd_ != NULL) {
+ SendMessage(keyboard_tooltip_hwnd_, WM_CLOSE, 0, 0);
+ keyboard_tooltip_hwnd_ = NULL;
+ }
+}
+
+void TooltipManager::DestroyKeyboardTooltipWindow(HWND window_to_destroy) {
+ if (keyboard_tooltip_hwnd_ == window_to_destroy)
+ HideKeyboardTooltip();
+}
+
+} // namespace views
diff --git a/views/widget/tooltip_manager.h b/views/widget/tooltip_manager.h
new file mode 100644
index 0000000..3d5620d
--- /dev/null
+++ b/views/widget/tooltip_manager.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_H_
+#define VIEWS_WIDGET_TOOLTIP_MANAGER_H_
+
+#include <windows.h>
+#include <commctrl.h>
+
+#include <string>
+#include "base/basictypes.h"
+#include "base/task.h"
+
+class ChromeFont;
+
+namespace views {
+
+class View;
+class Widget;
+
+// TooltipManager takes care of the wiring to support tooltips for Views.
+// This class is intended to be used by Widgets. 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 Widget and parent window.
+ TooltipManager(Widget* widget, 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);
+
+ // Invoked when toolbar icon gets focus.
+ void ShowKeyboardTooltip(View* view);
+
+ // Invoked when toolbar loses focus.
+ void HideKeyboardTooltip();
+
+ // 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);
+ // Not used directly by TooltipManager, but provided for AeroTooltipManager.
+ virtual void OnMouseLeave() {}
+
+ 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,
+ int position_x,
+ int position_y,
+ HWND window);
+
+ // Invoked when the timer elapses and tooltip has to be destroyed.
+ void DestroyKeyboardTooltipWindow(HWND window_to_destroy);
+
+ // Hosting Widget.
+ Widget* widget_;
+
+ // 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_;
+
+ // control window for tooltip displayed using keyboard.
+ HWND keyboard_tooltip_hwnd_;
+
+ // Used to register DestroyTooltipWindow function with PostDelayedTask
+ // function.
+ ScopedRunnableMethodFactory<TooltipManager> keyboard_tooltip_factory_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TooltipManager);
+};
+
+} // namespace views
+
+#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_H_
diff --git a/views/widget/widget.h b/views/widget/widget.h
new file mode 100644
index 0000000..8dd3f2e
--- /dev/null
+++ b/views/widget/widget.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_WIDGET_H_
+#define VIEWS_WIDGET_WIDGET_H_
+
+#include "base/gfx/native_widget_types.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+
+class Accelerator;
+class RootView;
+class TooltipManager;
+class Window;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Widget interface
+//
+// Widget is an abstract class that defines the API that should be implemented
+// by a native window in order to host a view hierarchy.
+//
+// Widget wraps a hierarchy of View objects (see view.h) that implement
+// painting and flexible layout within the bounds of the Widget's window.
+//
+// The Widget is responsible for handling various system events and forwarding
+// them to the appropriate view.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+class Widget {
+ public:
+ virtual ~Widget() { }
+
+ // Returns the bounds of this Widget in the screen coordinate system.
+ // If the receiving Widget 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 Widget is not a frame,
+ // including_frame is ignored.
+ virtual void GetBounds(gfx::Rect* out, bool including_frame) const = 0;
+
+ // Returns the gfx::NativeView associated with this Widget.
+ virtual gfx::NativeView GetNativeView() const = 0;
+
+ // Forces a paint of a specified rectangle immediately.
+ virtual void PaintNow(const gfx::Rect& update_rect) = 0;
+
+ // Returns the RootView contained by this Widget.
+ virtual RootView* GetRootView() = 0;
+
+ // Returns whether the Widget is visible to the user.
+ virtual bool IsVisible() const = 0;
+
+ // Returns whether the Widget is the currently active window.
+ virtual bool IsActive() const = 0;
+
+ // Returns the TooltipManager for this Widget. If this Widget 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,
+ Accelerator* accelerator) = 0;
+
+ // Returns the Window containing this Widget, or NULL if not contained in a
+ // window.
+ virtual Window* GetWindow() { return NULL; }
+ virtual const Window* GetWindow() const { return NULL; }
+};
+
+} // namespace views
+
+#endif // VIEWS_WIDGET_WIDGET_H_
diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc
new file mode 100644
index 0000000..25869f6
--- /dev/null
+++ b/views/widget/widget_gtk.cc
@@ -0,0 +1,386 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/widget_gtk.h"
+
+#include "views/fill_layout.h"
+#include "views/widget/root_view.h"
+
+namespace views {
+
+WidgetGtk::WidgetGtk()
+ : widget_(NULL),
+ is_mouse_down_(false),
+ last_mouse_event_was_move_(false) {
+}
+
+WidgetGtk::~WidgetGtk() {
+ gtk_widget_unref(widget_);
+
+ // MessageLoopForUI::current()->RemoveObserver(this);
+}
+
+void WidgetGtk::Init(const gfx::Rect& bounds,
+ bool has_own_focus_manager) {
+
+ // Force creation of the RootView if it hasn't been created yet.
+ GetRootView();
+
+ // Make container here.
+ widget_ = gtk_drawing_area_new();
+ gtk_drawing_area_size(GTK_DRAWING_AREA(widget_), 100, 100);
+ gtk_widget_show(widget_);
+
+ // Make sure we receive our motion events.
+ gtk_widget_set_events(widget_,
+ gtk_widget_get_events(widget_) |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK);
+
+ root_view_->OnWidgetCreated();
+
+ // TODO(port): if(has_own_focus_manager) block
+
+ SetViewForNative(widget_, this);
+ SetRootViewForWidget(widget_, root_view_.get());
+
+ // MessageLoopForUI::current()->AddObserver(this);
+
+ g_signal_connect_after(G_OBJECT(widget_), "size_allocate",
+ G_CALLBACK(CallSizeAllocate), NULL);
+ g_signal_connect(G_OBJECT(widget_), "expose_event",
+ G_CALLBACK(CallPaint), NULL);
+ g_signal_connect(G_OBJECT(widget_), "enter_notify_event",
+ G_CALLBACK(CallEnterNotify), NULL);
+ g_signal_connect(G_OBJECT(widget_), "leave_notify_event",
+ G_CALLBACK(CallLeaveNotify), NULL);
+ g_signal_connect(G_OBJECT(widget_), "motion_notify_event",
+ G_CALLBACK(CallMotionNotify), NULL);
+ g_signal_connect(G_OBJECT(widget_), "button_press_event",
+ G_CALLBACK(CallButtonPress), NULL);
+ g_signal_connect(G_OBJECT(widget_), "button_release_event",
+ G_CALLBACK(CallButtonRelease), NULL);
+ g_signal_connect(G_OBJECT(widget_), "focus_in_event",
+ G_CALLBACK(CallFocusIn), NULL);
+ g_signal_connect(G_OBJECT(widget_), "focus_out_event",
+ G_CALLBACK(CallFocusOut), NULL);
+ g_signal_connect(G_OBJECT(widget_), "key_press_event",
+ G_CALLBACK(CallKeyPress), NULL);
+ g_signal_connect(G_OBJECT(widget_), "key_release_event",
+ G_CALLBACK(CallKeyRelease), NULL);
+ g_signal_connect(G_OBJECT(widget_), "scroll_event",
+ G_CALLBACK(CallScroll), NULL);
+ g_signal_connect(G_OBJECT(widget_), "visibility_notify_event",
+ G_CALLBACK(CallVisibilityNotify), NULL);
+
+ // TODO(erg): Ignore these signals for now because they're such a drag.
+ //
+ // g_signal_connect(G_OBJECT(widget_), "drag_motion",
+ // G_CALLBACK(drag_motion_event_cb), NULL);
+ // g_signal_connect(G_OBJECT(widget_), "drag_leave",
+ // G_CALLBACK(drag_leave_event_cb), NULL);
+ // g_signal_connect(G_OBJECT(widget_), "drag_drop",
+ // G_CALLBACK(drag_drop_event_cb), NULL);
+ // g_signal_connect(G_OBJECT(widget_), "drag_data_received",
+ // G_CALLBACK(drag_data_received_event_cb), NULL);
+}
+
+void WidgetGtk::SetContentsView(View* view) {
+ DCHECK(view && widget_) << "Can't be called until after the HWND is created!";
+ // The ContentsView must be set up _after_ the window is created so that its
+ // Widget pointer is valid.
+ root_view_->SetLayoutManager(new FillLayout);
+ if (root_view_->GetChildViewCount() != 0)
+ root_view_->RemoveAllChildViews(true);
+ root_view_->AddChildView(view);
+
+ // TODO(erg): Terrible hack to work around lack of real sizing mechanics for
+ // now.
+ root_view_->SetBounds(0, 0, 100, 100);
+ root_view_->Layout();
+ root_view_->SchedulePaint();
+ NOTIMPLEMENTED();
+}
+
+void WidgetGtk::GetBounds(gfx::Rect* out, bool including_frame) const {
+ if (including_frame) {
+ NOTIMPLEMENTED();
+ *out = gfx::Rect();
+ return;
+ }
+
+ // TODO(erg): Not sure how to implement this. gtk_widget_size_request()
+ // returns a widget's requested size--not it's actual size. The system of
+ // containers and such do auto sizing tricks to make everything work within
+ // the constraints and requested sizes...
+ NOTIMPLEMENTED();
+}
+
+gfx::NativeView WidgetGtk::GetNativeView() const {
+ return widget_;
+}
+
+void WidgetGtk::PaintNow(const gfx::Rect& update_rect) {
+ // TODO(erg): This is woefully incomplete and is a straw man implementation.
+ gtk_widget_queue_draw_area(widget_, update_rect.x(), update_rect.y(),
+ update_rect.width(), update_rect.height());
+}
+
+RootView* WidgetGtk::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 WidgetGtk::IsVisible() const {
+ return GTK_WIDGET_VISIBLE(widget_);
+}
+
+bool WidgetGtk::IsActive() const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+TooltipManager* WidgetGtk::GetTooltipManager() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+bool WidgetGtk::GetAccelerator(int cmd_id, Accelerator* accelerator) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+gboolean WidgetGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) {
+ gfx::Point screen_loc(event->x_root, event->y_root);
+ 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 false;
+ }
+ 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,
+ event->x,
+ event->y,
+ Event::GetFlagsFromGdkState(event->state));
+ root_view_->OnMouseMoved(mouse_move);
+ return true;
+}
+
+gboolean WidgetGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event) {
+ return ProcessMousePressed(event);
+}
+
+gboolean WidgetGtk::OnButtonRelease(GtkWidget* widget, GdkEventButton* event) {
+ ProcessMouseReleased(event);
+ return true;
+}
+
+gboolean WidgetGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) {
+ root_view_->OnPaint(event);
+ return true;
+}
+
+gboolean WidgetGtk::OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event) {
+ // TODO(port): We may not actually need this message; it looks like
+ // OnNotificationNotify() takes care of this case...
+ return false;
+}
+
+gboolean WidgetGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) {
+ last_mouse_event_was_move_ = false;
+ root_view_->ProcessOnMouseExited();
+ return true;
+}
+
+gboolean WidgetGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) {
+ KeyEvent key_event(event);
+ return root_view_->ProcessKeyEvent(key_event);
+}
+
+gboolean WidgetGtk::OnKeyRelease(GtkWidget* widget, GdkEventKey* event) {
+ KeyEvent key_event(event);
+ return root_view_->ProcessKeyEvent(key_event);
+}
+
+RootView* WidgetGtk::CreateRootView() {
+ return new RootView(this);
+}
+
+bool WidgetGtk::ProcessMousePressed(GdkEventButton* event) {
+ last_mouse_event_was_move_ = false;
+ MouseEvent mouse_pressed(Event::ET_MOUSE_PRESSED,
+ event->x, event->y,
+// (dbl_click ? MouseEvent::EF_IS_DOUBLE_CLICK : 0) |
+ Event::GetFlagsFromGdkState(event->state));
+ if (root_view_->OnMousePressed(mouse_pressed)) {
+ is_mouse_down_ = true;
+ // TODO(port): Enable this once I figure out what capture is.
+ // if (!has_capture_) {
+ // SetCapture();
+ // has_capture_ = true;
+ // current_action_ = FA_FORWARDING;
+ // }
+ return true;
+ }
+
+ return false;
+}
+
+void WidgetGtk::ProcessMouseReleased(GdkEventButton* event) {
+ last_mouse_event_was_move_ = false;
+ MouseEvent mouse_up(Event::ET_MOUSE_RELEASED,
+ event->x, event->y,
+ Event::GetFlagsFromGdkState(event->state));
+ // Release the capture first, that way we don't get confused if
+ // OnMouseReleased blocks.
+ //
+ // TODO(port): Enable this once I figure out what capture is.
+ // if (has_capture_ && ReleaseCaptureOnMouseReleased()) {
+ // has_capture_ = false;
+ // current_action_ = FA_NONE;
+ // ReleaseCapture();
+ // }
+ is_mouse_down_ = false;
+ root_view_->OnMouseReleased(mouse_up, false);
+}
+
+// static
+WidgetGtk* WidgetGtk::GetViewForNative(GtkWidget* widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "chrome-views");
+ return static_cast<WidgetGtk*>(user_data);
+}
+
+// static
+void WidgetGtk::SetViewForNative(GtkWidget* widget, WidgetGtk* view) {
+ g_object_set_data(G_OBJECT(widget), "chrome-views", view);
+}
+
+// static
+RootView* WidgetGtk::GetRootViewForWidget(GtkWidget* widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "root-view");
+ return static_cast<RootView*>(user_data);
+}
+
+// static
+void WidgetGtk::SetRootViewForWidget(GtkWidget* widget, RootView* root_view) {
+ g_object_set_data(G_OBJECT(widget), "root-view", root_view);
+}
+
+// static
+void WidgetGtk::CallSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return;
+
+ widget_gtk->OnSizeAllocate(widget, allocation);
+}
+
+gboolean WidgetGtk::CallPaint(GtkWidget* widget, GdkEventExpose* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnPaint(widget, event);
+}
+
+gboolean WidgetGtk::CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnEnterNotify(widget, event);
+}
+
+gboolean WidgetGtk::CallLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnLeaveNotify(widget, event);
+}
+
+gboolean WidgetGtk::CallMotionNotify(GtkWidget* widget, GdkEventMotion* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnMotionNotify(widget, event);
+}
+
+gboolean WidgetGtk::CallButtonPress(GtkWidget* widget, GdkEventButton* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnButtonPress(widget, event);
+}
+
+gboolean WidgetGtk::CallButtonRelease(GtkWidget* widget, GdkEventButton* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnButtonRelease(widget, event);
+}
+
+gboolean WidgetGtk::CallFocusIn(GtkWidget* widget, GdkEventFocus* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnFocusIn(widget, event);
+}
+
+gboolean WidgetGtk::CallFocusOut(GtkWidget* widget, GdkEventFocus* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnFocusOut(widget, event);
+}
+
+gboolean WidgetGtk::CallKeyPress(GtkWidget* widget, GdkEventKey* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnKeyPress(widget, event);
+}
+
+gboolean WidgetGtk::CallKeyRelease(GtkWidget* widget, GdkEventKey* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnKeyRelease(widget, event);
+}
+
+gboolean WidgetGtk::CallScroll(GtkWidget* widget, GdkEventScroll* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnScroll(widget, event);
+}
+
+gboolean WidgetGtk::CallVisibilityNotify(GtkWidget* widget,
+ GdkEventVisibility* event) {
+ WidgetGtk* widget_gtk = GetViewForNative(widget);
+ if (!widget_gtk)
+ return false;
+
+ return widget_gtk->OnVisibilityNotify(widget, event);
+}
+
+}
diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h
new file mode 100644
index 0000000..4bbc0dd
--- /dev/null
+++ b/views/widget/widget_gtk.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_WIDGET_GTK_H_
+#define VIEWS_WIDGET_WIDGET_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include "base/message_loop.h"
+#include "views/widget/widget.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+
+class View;
+
+class WidgetGtk : public Widget {
+ public:
+ static WidgetGtk* Construct() {
+ // This isn't used, but exists to force WidgetGtk to be instantiable.
+ return new WidgetGtk;
+ }
+
+ WidgetGtk();
+ virtual ~WidgetGtk();
+
+ // Initializes this widget and returns the gtk drawing area for the caller to
+ // add to its hierarchy. (We can't pass in the parent to this method because
+ // there are no standard adding semantics in gtk...)
+ void Init(const gfx::Rect& bounds, bool has_own_focus_manager);
+
+ virtual void SetContentsView(View* view);
+
+ // Overridden from Widget:
+ virtual void GetBounds(gfx::Rect* out, bool including_frame) const;
+ virtual gfx::NativeView GetNativeView() const;
+ virtual void PaintNow(const gfx::Rect& update_rect);
+ virtual RootView* GetRootView();
+ virtual bool IsVisible() const;
+ virtual bool IsActive() const;
+ virtual TooltipManager* GetTooltipManager();
+ virtual bool GetAccelerator(int cmd_id, Accelerator* accelerator);
+
+ protected:
+ virtual void OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {}
+ virtual gboolean OnPaint(GtkWidget* widget, GdkEventExpose* event);
+ virtual gboolean OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event);
+ virtual gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event);
+ virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event);
+ virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event);
+ virtual gboolean OnButtonRelease(GtkWidget* widget, GdkEventButton* event);
+ virtual gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* event) {
+ return false;
+ }
+ virtual gboolean OnFocusOut(GtkWidget* widget, GdkEventFocus* event) {
+ return false;
+ }
+ virtual gboolean OnKeyPress(GtkWidget* widget, GdkEventKey* event);
+ virtual gboolean OnKeyRelease(GtkWidget* widget, GdkEventKey* event);
+ virtual gboolean OnScroll(GtkWidget* widget, GdkEventScroll* event) {
+ return false;
+ }
+ virtual gboolean OnVisibilityNotify(GtkWidget* widget,
+ GdkEventVisibility* event) {
+ return false;
+ }
+
+ private:
+ virtual RootView* CreateRootView();
+
+ // Process a mouse click
+ bool ProcessMousePressed(GdkEventButton* event);
+ void ProcessMouseReleased(GdkEventButton* event);
+
+ // Sets and retrieves the WidgetGtk in the userdata section of the widget.
+ static WidgetGtk* GetViewForNative(GtkWidget* widget);
+ static void SetViewForNative(GtkWidget* widget, WidgetGtk* view);
+
+ static RootView* GetRootViewForWidget(GtkWidget* widget);
+ static void SetRootViewForWidget(GtkWidget* widget, RootView* root_view);
+
+ // A set of static signal handlers that bridge
+ static void CallSizeAllocate(GtkWidget* widget, GtkAllocation* allocation);
+ static gboolean CallPaint(GtkWidget* widget, GdkEventExpose* event);
+ static gboolean CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event);
+ static gboolean CallLeaveNotify(GtkWidget* widget, GdkEventCrossing* event);
+ static gboolean CallMotionNotify(GtkWidget* widget, GdkEventMotion* event);
+ static gboolean CallButtonPress(GtkWidget* widget, GdkEventButton* event);
+ static gboolean CallButtonRelease(GtkWidget* widget, GdkEventButton* event);
+ static gboolean CallFocusIn(GtkWidget* widget, GdkEventFocus* event);
+ static gboolean CallFocusOut(GtkWidget* widget, GdkEventFocus* event);
+ static gboolean CallKeyPress(GtkWidget* widget, GdkEventKey* event);
+ static gboolean CallKeyRelease(GtkWidget* widget, GdkEventKey* event);
+ static gboolean CallScroll(GtkWidget* widget, GdkEventScroll* event);
+ static gboolean CallVisibilityNotify(GtkWidget* widget,
+ GdkEventVisibility* event);
+
+ // Our native view.
+ GtkWidget* widget_;
+
+ // The root of the View hierarchy attached to this window.
+ scoped_ptr<RootView> root_view_;
+
+ // If true, the mouse is currently down.
+ bool is_mouse_down_;
+
+ // 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 screen coordinates.
+ int last_mouse_move_x_;
+ int last_mouse_move_y_;
+};
+
+} // namespace views
+
+#endif // VIEWS_WIDGET_WIDGET_GTK_H_
diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc
new file mode 100644
index 0000000..2bc9b0c
--- /dev/null
+++ b/views/widget/widget_win.cc
@@ -0,0 +1,999 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/widget/widget_win.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "base/gfx/native_theme.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/common/win_util.h"
+#include "views/accessibility/view_accessibility.h"
+#include "views/controls/native_control_win.h"
+#include "views/fill_layout.h"
+#include "views/focus/focus_util_win.h"
+#include "views/widget/aero_tooltip_manager.h"
+#include "views/widget/root_view.h"
+#include "views/window/window_win.h"
+
+namespace views {
+
+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));
+}
+
+NativeControlWin* GetNativeControlWinForHWND(HWND hwnd) {
+ return reinterpret_cast<NativeControlWin*>(
+ ::GetProp(hwnd, NativeControlWin::kNativeControlWinKey));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Window class tracking.
+
+// static
+const wchar_t* const WidgetWin::kBaseClassName =
+ L"Chrome_WidgetWin_";
+
+// Window class information used for registering unique windows.
+struct ClassInfo {
+ UINT style;
+ HBRUSH background;
+
+ explicit ClassInfo(int style)
+ : style(style),
+ background(NULL) {}
+
+ // Compares two ClassInfos. Returns true if all members match.
+ bool Equals(const ClassInfo& other) const {
+ return (other.style == style && other.background == background);
+ }
+};
+
+class ClassRegistrar {
+ public:
+ ~ClassRegistrar() {
+ for (RegisteredClasses::iterator i = registered_classes_.begin();
+ i != registered_classes_.end(); ++i) {
+ UnregisterClass(i->name.c_str(), NULL);
+ }
+ }
+
+ // Puts the name for the class matching |class_info| in |class_name|, creating
+ // a new name if the class is not yet known.
+ // Returns true if this class was already known, false otherwise.
+ bool RetrieveClassName(const ClassInfo& class_info, std::wstring* name) {
+ for (RegisteredClasses::const_iterator i = registered_classes_.begin();
+ i != registered_classes_.end(); ++i) {
+ if (class_info.Equals(i->info)) {
+ name->assign(i->name);
+ return true;
+ }
+ }
+
+ name->assign(std::wstring(WidgetWin::kBaseClassName) +
+ IntToWString(registered_count_++));
+ return false;
+ }
+
+ void RegisterClass(const ClassInfo& class_info,
+ const std::wstring& name,
+ ATOM atom) {
+ registered_classes_.push_back(RegisteredClass(class_info, name, atom));
+ }
+
+ private:
+ // 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;
+ };
+
+ ClassRegistrar() : registered_count_(0) { }
+ friend struct DefaultSingletonTraits<ClassRegistrar>;
+
+ typedef std::list<RegisteredClass> RegisteredClasses;
+ RegisteredClasses registered_classes_;
+
+ // Counter of how many classes have ben registered so far.
+ int registered_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClassRegistrar);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// WidgetWin, public
+
+WidgetWin::WidgetWin()
+ : close_widget_factory_(this),
+ active_mouse_tracking_flags_(0),
+ has_capture_(false),
+ current_action_(FA_NONE),
+ window_style_(0),
+ window_ex_style_(kWindowDefaultExStyle),
+ use_layered_buffer_(true),
+ layered_alpha_(255),
+ delete_on_destroy_(true),
+ can_update_layered_window_(true),
+ last_mouse_event_was_move_(false),
+ is_mouse_down_(false),
+ is_window_(false),
+ class_style_(CS_DBLCLKS),
+ hwnd_(NULL) {
+}
+
+WidgetWin::~WidgetWin() {
+ MessageLoopForUI::current()->RemoveObserver(this);
+}
+
+void WidgetWin::Init(HWND parent, const gfx::Rect& bounds,
+ bool has_own_focus_manager) {
+ if (window_style_ == 0)
+ window_style_ = parent ? kWindowDefaultChildStyle : kWindowDefaultStyle;
+
+ // See if the style has been overridden.
+ opaque_ = !(window_ex_style_ & WS_EX_TRANSPARENT);
+ use_layered_buffer_ = (use_layered_buffer_ &&
+ !!(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_);
+ TRACK_HWND_CREATION(hwnd_);
+ SetWindowSupportsRerouteMouseWheel(hwnd_);
+
+ // The window procedure should have set the data for us.
+ DCHECK(win_util::GetWindowUserData(hwnd_) == this);
+
+ root_view_->OnWidgetCreated();
+
+ if (has_own_focus_manager) {
+ 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);
+ }
+
+ // Sets the RootView as a property, so the automation can introspect windows.
+ SetRootViewForHWND(hwnd_, root_view_.get());
+
+ MessageLoopForUI::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, GetNativeView()));
+ } else {
+ tooltip_manager_.reset(new TooltipManager(this, GetNativeView()));
+ }
+
+ // This message initializes the window so that focus border are shown for
+ // windows.
+ ::SendMessage(GetNativeView(),
+ 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(GetNativeView(), NULL, 0);
+}
+
+void WidgetWin::SetContentsView(View* view) {
+ DCHECK(view && hwnd_) << "Can't be called until after the HWND is created!";
+ // The ContentsView must be set up _after_ the window is created so that its
+ // Widget pointer is valid.
+ root_view_->SetLayoutManager(new FillLayout);
+ if (root_view_->GetChildViewCount() != 0)
+ root_view_->RemoveAllChildViews(true);
+ root_view_->AddChildView(view);
+
+ // Manually size the window here to ensure the root view is laid out.
+ RECT wr;
+ GetWindowRect(&wr);
+ ChangeSize(0, CSize(wr.right - wr.left, wr.bottom - wr.top));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Widget implementation:
+
+void WidgetWin::GetBounds(gfx::Rect* out, bool including_frame) const {
+ CRect crect;
+ if (including_frame) {
+ GetWindowRect(&crect);
+ *out = gfx::Rect(crect);
+ return;
+ }
+
+ GetClientRect(&crect);
+ POINT p = {0, 0};
+ ::ClientToScreen(hwnd_, &p);
+ out->SetRect(crect.left + p.x, crect.top + p.y,
+ crect.Width(), crect.Height());
+}
+
+gfx::NativeView WidgetWin::GetNativeView() const {
+ return hwnd_;
+}
+
+void WidgetWin::PaintNow(const gfx::Rect& update_rect) {
+ if (use_layered_buffer_) {
+ 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.ToRECT();
+ 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 {
+ RECT native_update_rect = update_rect.ToRECT();
+ RedrawWindow(hwnd_, &native_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* WidgetWin::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 WidgetWin::IsVisible() const {
+ return !!::IsWindowVisible(GetNativeView());
+}
+
+bool WidgetWin::IsActive() const {
+ return win_util::IsWindowActive(GetNativeView());
+}
+
+TooltipManager* WidgetWin::GetTooltipManager() {
+ return tooltip_manager_.get();
+}
+
+Window* WidgetWin::GetWindow() {
+ return GetWindowImpl(hwnd_);
+}
+
+const Window* WidgetWin::GetWindow() const {
+ return GetWindowImpl(hwnd_);
+}
+
+void WidgetWin::SetLayeredAlpha(BYTE layered_alpha) {
+ layered_alpha_ = layered_alpha;
+
+// if (hwnd_)
+// UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC());
+}
+
+void WidgetWin::SetUseLayeredBuffer(bool use_layered_buffer) {
+ if (use_layered_buffer_ == use_layered_buffer)
+ return;
+
+ use_layered_buffer_ = use_layered_buffer;
+ if (!hwnd_)
+ return;
+
+ if (use_layered_buffer_) {
+ // Force creation of the buffer at the right size.
+ RECT wr;
+ GetWindowRect(&wr);
+ ChangeSize(0, CSize(wr.right - wr.left, wr.bottom - wr.top));
+ } else {
+ contents_.reset(NULL);
+ }
+}
+
+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* WidgetWin::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 WidgetWin::Close() {
+ if (!IsWindow())
+ return; // No need to do anything.
+
+ // Let's hide ourselves right away.
+ Hide();
+ if (close_widget_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_widget_factory_.NewRunnableMethod(
+ &WidgetWin::CloseNow));
+ }
+}
+
+void WidgetWin::Hide() {
+ if (IsWindow()) {
+ // 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 WidgetWin::Show() {
+ if (IsWindow())
+ ShowWindow(SW_SHOWNOACTIVATE);
+}
+
+void WidgetWin::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 WidgetWin::WillProcessMessage(const MSG& msg) {
+}
+
+void WidgetWin::DidProcessMessage(const MSG& msg) {
+ if (root_view_->NeedsPainting(true)) {
+ PaintNow(root_view_->GetScheduledPaintRect());
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FocusTraversable
+
+View* WidgetWin::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* WidgetWin::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 WidgetWin::SetFocusTraversableParent(FocusTraversable* parent) {
+ root_view_->SetFocusTraversableParent(parent);
+}
+
+View* WidgetWin::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 WidgetWin::SetFocusTraversableParentView(View* parent_view) {
+ root_view_->SetFocusTraversableParentView(parent_view);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Message handlers
+
+void WidgetWin::OnCaptureChanged(HWND hwnd) {
+ if (has_capture_) {
+ if (is_mouse_down_)
+ root_view_->ProcessMouseDragCanceled();
+ is_mouse_down_ = false;
+ has_capture_ = false;
+ }
+}
+
+void WidgetWin::OnClose() {
+ Close();
+}
+
+void WidgetWin::OnDestroy() {
+ root_view_->OnWidgetDestroyed();
+
+ RemoveProp(hwnd_, kRootViewWindowProperty);
+}
+
+LRESULT WidgetWin::OnEraseBkgnd(HDC dc) {
+ // This is needed for magical win32 flicker ju-ju
+ return 1;
+}
+
+LRESULT WidgetWin::OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param) {
+ LRESULT reference_result = static_cast<LRESULT>(0L);
+
+ // Accessibility readers will send an OBJID_CLIENT message
+ if (OBJID_CLIENT == l_param) {
+ // If our MSAA root is already created, reuse that pointer. Otherwise,
+ // create a new one.
+ if (!accessibility_root_) {
+ CComObject<ViewAccessibility>* instance = NULL;
+
+ HRESULT hr = CComObject<ViewAccessibility>::CreateInstance(&instance);
+ DCHECK(SUCCEEDED(hr));
+
+ if (!instance) {
+ // Return with failure.
+ return static_cast<LRESULT>(0L);
+ }
+
+ CComPtr<IAccessible> accessibility_instance(instance);
+
+ if (!SUCCEEDED(instance->Initialize(root_view_.get()))) {
+ // Return with failure.
+ return static_cast<LRESULT>(0L);
+ }
+
+ // All is well, assign the temp instance to the class smart pointer
+ accessibility_root_.Attach(accessibility_instance.Detach());
+
+ if (!accessibility_root_) {
+ // Return with failure.
+ return static_cast<LRESULT>(0L);
+ }
+
+ // Notify that an instance of IAccessible was allocated for m_hWnd
+ ::NotifyWinEvent(EVENT_OBJECT_CREATE, GetNativeView(), OBJID_CLIENT,
+ CHILDID_SELF);
+ }
+
+ // Create a reference to ViewAccessibility that MSAA will marshall
+ // to the client.
+ reference_result = LresultFromObject(IID_IAccessible, w_param,
+ static_cast<IAccessible*>(accessibility_root_));
+ }
+ return reference_result;
+}
+
+void WidgetWin::OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags) {
+ KeyEvent event(Event::ET_KEY_PRESSED, c, rep_cnt, flags);
+ SetMsgHandled(root_view_->ProcessKeyEvent(event));
+}
+
+void WidgetWin::OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags) {
+ KeyEvent event(Event::ET_KEY_RELEASED, c, rep_cnt, flags);
+ SetMsgHandled(root_view_->ProcessKeyEvent(event));
+}
+
+void WidgetWin::OnLButtonDown(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_LBUTTON, false, false);
+}
+
+void WidgetWin::OnLButtonUp(UINT flags, const CPoint& point) {
+ ProcessMouseReleased(point, flags | MK_LBUTTON);
+}
+
+void WidgetWin::OnLButtonDblClk(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_LBUTTON, true, false);
+}
+
+void WidgetWin::OnMButtonDown(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_MBUTTON, false, false);
+}
+
+void WidgetWin::OnMButtonUp(UINT flags, const CPoint& point) {
+ ProcessMouseReleased(point, flags | MK_MBUTTON);
+}
+
+void WidgetWin::OnMButtonDblClk(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_MBUTTON, true, false);
+}
+
+LRESULT WidgetWin::OnMouseActivate(HWND window, UINT hittest_code,
+ UINT message) {
+ SetMsgHandled(FALSE);
+ return MA_ACTIVATE;
+}
+
+void WidgetWin::OnMouseMove(UINT flags, const CPoint& point) {
+ ProcessMouseMoved(point, flags, false);
+}
+
+LRESULT WidgetWin::OnMouseLeave(UINT message, WPARAM w_param, LPARAM l_param) {
+ tooltip_manager_->OnMouseLeave();
+ ProcessMouseExited();
+ return 0;
+}
+
+LRESULT WidgetWin::OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param) {
+ // Reroute the mouse-wheel to the window under the mouse pointer if
+ // applicable.
+ if (message == WM_MOUSEWHEEL &&
+ views::RerouteMouseWheel(hwnd_, w_param, l_param)) {
+ return 0;
+ }
+
+ int flags = GET_KEYSTATE_WPARAM(w_param);
+ short distance = GET_WHEEL_DELTA_WPARAM(w_param);
+ int x = GET_X_LPARAM(l_param);
+ int y = GET_Y_LPARAM(l_param);
+ MouseWheelEvent e(distance, x, y, Event::ConvertWindowsFlags(flags));
+ return root_view_->ProcessMouseWheelEvent(e) ? 0 : 1;
+}
+
+LRESULT WidgetWin::OnMouseRange(UINT msg, WPARAM w_param, LPARAM l_param) {
+ tooltip_manager_->OnMouse(msg, w_param, l_param);
+ SetMsgHandled(FALSE);
+ return 0;
+}
+
+void WidgetWin::OnNCLButtonDblClk(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_LBUTTON, true, true));
+}
+
+void WidgetWin::OnNCLButtonDown(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_LBUTTON, false, true));
+}
+
+void WidgetWin::OnNCLButtonUp(UINT flags, const CPoint& point) {
+ SetMsgHandled(FALSE);
+}
+
+void WidgetWin::OnNCMButtonDblClk(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_MBUTTON, true, true));
+}
+
+void WidgetWin::OnNCMButtonDown(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_MBUTTON, false, true));
+}
+
+void WidgetWin::OnNCMButtonUp(UINT flags, const CPoint& point) {
+ SetMsgHandled(FALSE);
+}
+
+LRESULT WidgetWin::OnNCMouseLeave(UINT uMsg, WPARAM w_param, LPARAM l_param) {
+ ProcessMouseExited();
+ return 0;
+}
+
+LRESULT WidgetWin::OnNCMouseMove(UINT flags, const CPoint& point) {
+ // NC points are in screen coordinates.
+ CPoint temp = point;
+ MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1);
+ ProcessMouseMoved(temp, 0, true);
+
+ // 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;
+}
+
+void WidgetWin::OnNCRButtonDblClk(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_RBUTTON, true, true));
+}
+
+void WidgetWin::OnNCRButtonDown(UINT flags, const CPoint& point) {
+ SetMsgHandled(ProcessMousePressed(point, flags | MK_RBUTTON, false, true));
+}
+
+void WidgetWin::OnNCRButtonUp(UINT flags, const CPoint& point) {
+ SetMsgHandled(FALSE);
+}
+
+LRESULT WidgetWin::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 WidgetWin::OnPaint(HDC dc) {
+ root_view_->OnPaint(GetNativeView());
+}
+
+void WidgetWin::OnRButtonDown(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_RBUTTON, false, false);
+}
+
+void WidgetWin::OnRButtonUp(UINT flags, const CPoint& point) {
+ ProcessMouseReleased(point, flags | MK_RBUTTON);
+}
+
+void WidgetWin::OnRButtonDblClk(UINT flags, const CPoint& point) {
+ ProcessMousePressed(point, flags | MK_RBUTTON, true, false);
+}
+
+void WidgetWin::OnSize(UINT param, const CSize& size) {
+ ChangeSize(param, size);
+}
+
+void WidgetWin::OnThemeChanged() {
+ // Notify NativeTheme.
+ gfx::NativeTheme::instance()->CloseHandles();
+}
+
+void WidgetWin::OnFinalMessage(HWND window) {
+ if (delete_on_destroy_)
+ delete this;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// WidgetWin, protected:
+
+void WidgetWin::TrackMouseEvents(DWORD mouse_tracking_flags) {
+ // 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 (active_mouse_tracking_flags_ == 0 || mouse_tracking_flags & TME_CANCEL) {
+ if (mouse_tracking_flags & TME_CANCEL) {
+ // We're about to cancel active mouse tracking, so empty out the stored
+ // state.
+ active_mouse_tracking_flags_ = 0;
+ } else {
+ active_mouse_tracking_flags_ = mouse_tracking_flags;
+ }
+
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(tme);
+ tme.dwFlags = mouse_tracking_flags;
+ tme.hwndTrack = GetNativeView();
+ tme.dwHoverTime = 0;
+ TrackMouseEvent(&tme);
+ } else if (mouse_tracking_flags != active_mouse_tracking_flags_) {
+ TrackMouseEvents(active_mouse_tracking_flags_ | TME_CANCEL);
+ TrackMouseEvents(mouse_tracking_flags);
+ }
+}
+
+bool WidgetWin::ProcessMousePressed(const CPoint& point,
+ UINT flags,
+ bool dbl_click,
+ bool non_client) {
+ last_mouse_event_was_move_ = false;
+ // Windows gives screen coordinates for nonclient events, while the RootView
+ // expects window coordinates; convert if necessary.
+ gfx::Point converted_point(point);
+ if (non_client)
+ View::ConvertPointToView(NULL, root_view_.get(), &converted_point);
+ MouseEvent mouse_pressed(Event::ET_MOUSE_PRESSED,
+ converted_point.x(),
+ converted_point.y(),
+ (dbl_click ? MouseEvent::EF_IS_DOUBLE_CLICK : 0) |
+ (non_client ? MouseEvent::EF_IS_NON_CLIENT : 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 WidgetWin::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 WidgetWin::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 WidgetWin::ProcessMouseMoved(const CPoint &point, UINT flags,
+ bool is_nonclient) {
+ // 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(is_nonclient ? TME_NONCLIENT | TME_LEAVE : TME_LEAVE);
+ if (has_capture_ && is_mouse_down_) {
+ ProcessMouseDragged(point, flags);
+ } else {
+ gfx::Point 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 WidgetWin::ProcessMouseExited() {
+ last_mouse_event_was_move_ = false;
+ root_view_->ProcessOnMouseExited();
+ // Reset our tracking flag so that future mouse movement over this WidgetWin
+ // results in a new tracking session.
+ active_mouse_tracking_flags_ = 0;
+}
+
+void WidgetWin::ChangeSize(UINT size_param, const CSize& size) {
+ CRect rect;
+ if (use_layered_buffer_) {
+ GetWindowRect(&rect);
+ SizeContents(rect);
+ } else {
+ GetClientRect(&rect);
+ }
+
+ // Resizing changes the size of the view hierarchy and thus forces a
+ // complete relayout.
+ root_view_->SetBounds(0, 0, rect.Width(), rect.Height());
+ root_view_->Layout();
+ root_view_->SchedulePaint();
+
+ if (use_layered_buffer_)
+ PaintNow(gfx::Rect(rect));
+}
+
+RootView* WidgetWin::CreateRootView() {
+ return new RootView(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// WidgetWin, private:
+
+// static
+Window* WidgetWin::GetWindowImpl(HWND hwnd) {
+ // NOTE: we can't use GetAncestor here as constrained windows are a Window,
+ // but not a top level window.
+ HWND parent = hwnd;
+ while (parent) {
+ WidgetWin* widget =
+ reinterpret_cast<WidgetWin*>(win_util::GetWindowUserData(parent));
+ if (widget && widget->is_window_)
+ return static_cast<WindowWin*>(widget);
+ parent = ::GetParent(parent);
+ }
+ return NULL;
+}
+
+void WidgetWin::SizeContents(const CRect& window_rect) {
+ contents_.reset(new ChromeCanvas(window_rect.Width(),
+ window_rect.Height(),
+ false));
+}
+
+void WidgetWin::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);
+ gfx::Rect dirty_rect = root_view_->GetScheduledPaintRect();
+ contents_->ClipRectInt(dirty_rect.x(), dirty_rect.y(), dirty_rect.width(),
+ dirty_rect.height());
+ root_view_->ProcessPaint(contents_.get());
+ contents_->restore();
+
+ UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC());
+}
+
+void WidgetWin::UpdateWindowFromContents(HDC dib_dc) {
+ DCHECK(use_layered_buffer_);
+ 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 WidgetWin::GetWindowClassName() {
+ ClassInfo class_info(initial_class_style());
+ std::wstring name;
+ if (Singleton<ClassRegistrar>()->RetrieveClassName(class_info, &name))
+ return name;
+
+ // No class found, need to register one.
+ WNDCLASSEX class_ex;
+ class_ex.cbSize = sizeof(WNDCLASSEX);
+ class_ex.style = class_info.style;
+ class_ex.lpfnWndProc = &WidgetWin::WndProc;
+ class_ex.cbClsExtra = 0;
+ class_ex.cbWndExtra = 0;
+ class_ex.hInstance = NULL;
+ class_ex.hIcon = LoadIcon(GetModuleHandle(L"chrome.dll"),
+ MAKEINTRESOURCE(IDR_MAINFRAME));
+ 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_ex.hIcon;
+ ATOM atom = RegisterClassEx(&class_ex);
+ DCHECK(atom);
+
+ Singleton<ClassRegistrar>()->RegisterClass(class_info, name, atom);
+
+ return name;
+}
+
+// Get the source HWND of the specified message. Depending on the message, the
+// source HWND is encoded in either the WPARAM or the LPARAM value.
+HWND GetControlHWNDForMessage(UINT message, WPARAM w_param, LPARAM l_param) {
+ // Each of the following messages can be sent by a child HWND and must be
+ // forwarded to its associated NativeControlWin for handling.
+ switch (message) {
+ case WM_NOTIFY:
+ return reinterpret_cast<NMHDR*>(l_param)->hwndFrom;
+ case WM_COMMAND:
+ return reinterpret_cast<HWND>(l_param);
+ case WM_CONTEXTMENU:
+ return reinterpret_cast<HWND>(w_param);
+ case WM_CTLCOLORBTN:
+ case WM_CTLCOLORSTATIC:
+ return reinterpret_cast<HWND>(l_param);
+ }
+ return NULL;
+}
+
+// Some messages may be sent to us by a child HWND managed by
+// NativeControlWin. If this is the case, this function will forward those
+// messages on to the object associated with the source HWND and return true,
+// in which case the window procedure must not do any further processing of
+// the message. If there is no associated NativeControlWin, the return value
+// will be false and the WndProc can continue processing the message normally.
+// |l_result| contains the result of the message processing by the control and
+// must be returned by the WndProc if the return value is true.
+bool ProcessNativeControlMessage(UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* l_result) {
+ *l_result = 0;
+
+ HWND control_hwnd = GetControlHWNDForMessage(message, w_param, l_param);
+ if (IsWindow(control_hwnd)) {
+ NativeControlWin* wrapper = GetNativeControlWinForHWND(control_hwnd);
+ if (wrapper)
+ return wrapper->ProcessMessage(message, w_param, l_param, l_result);
+ }
+
+ return false;
+}
+
+// static
+LRESULT CALLBACK WidgetWin::WndProc(HWND window, UINT message,
+ WPARAM w_param, LPARAM l_param) {
+ if (message == WM_NCCREATE) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(l_param);
+ WidgetWin* widget = reinterpret_cast<WidgetWin*>(cs->lpCreateParams);
+ DCHECK(widget);
+ win_util::SetWindowUserData(window, widget);
+ widget->hwnd_ = window;
+ return TRUE;
+ }
+
+ WidgetWin* widget = reinterpret_cast<WidgetWin*>(
+ win_util::GetWindowUserData(window));
+ if (!widget)
+ return 0;
+
+ LRESULT result = 0;
+
+ // First allow messages sent by child controls to be processed directly by
+ // their associated views. If such a view is present, it will handle the
+ // message *instead of* this WidgetWin.
+ if (ProcessNativeControlMessage(message, w_param, l_param, &result))
+ return result;
+
+ // Otherwise we handle everything else.
+ if (!widget->ProcessWindowMessage(window, message, w_param, l_param, result))
+ result = DefWindowProc(window, message, w_param, l_param);
+ if (message == WM_NCDESTROY) {
+ TRACK_HWND_DESTRUCTION(window);
+ widget->hwnd_ = NULL;
+ widget->OnFinalMessage(window);
+ }
+ return result;
+}
+
+} // namespace views
diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h
new file mode 100644
index 0000000..09efa21
--- /dev/null
+++ b/views/widget/widget_win.h
@@ -0,0 +1,636 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WIDGET_WIDGET_WIN_H_
+#define VIEWS_WIDGET_WIDGET_WIN_H_
+
+#include <atlbase.h>
+#include <atlcrack.h>
+
+#include "base/message_loop.h"
+#include "base/system_monitor.h"
+#include "views/focus/focus_manager.h"
+#include "views/layout_manager.h"
+#include "views/widget/widget.h"
+
+class ChromeCanvas;
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+
+class RootView;
+class TooltipManager;
+class Window;
+
+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;
+
+// These two messages aren't defined in winuser.h, but they are sent to windows
+// with captions. They appear to paint the window caption and frame.
+// Unfortunately if you override the standard non-client rendering as we do
+// with CustomFrameWindow, sometimes Windows (not deterministically
+// reproducibly but definitely frequently) will send these messages to the
+// window and paint the standard caption/title over the top of the custom one.
+// So we need to handle these messages in CustomFrameWindow to prevent this
+// from happening.
+static const int WM_NCUAHDRAWCAPTION = 0xAE;
+static const int WM_NCUAHDRAWFRAME = 0xAF;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// WidgetWin
+// A Widget for a views 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 Widget contains a RootView which owns the hierarchy of views within it.
+// As long as views 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+class WidgetWin : public Widget,
+ public MessageLoopForUI::Observer,
+ public FocusTraversable,
+ public AcceleratorTarget {
+ public:
+ WidgetWin();
+ virtual ~WidgetWin();
+
+ // Initialize the Widget with a parent and an initial desired size.
+ // |contents_view| is the view that will be the single child of RootView
+ // within this Widget. 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,
+ bool has_own_focus_manager);
+
+ // Sets the specified view as the contents of this Widget. There can only
+ // be one contnets view child of this Widget's RootView. This view is sized to
+ // fit the entire size of the RootView. The RootView takes ownership of this
+ // View, unless it is set as not being parent-owned.
+ virtual void SetContentsView(View* view);
+
+ // 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);
+
+ // See description of use_layered_buffer_ for details.
+ void SetUseLayeredBuffer(bool use_layered_buffer);
+
+ // 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();
+
+ // Shows the window without changing size/position/activation state.
+ virtual void Show();
+
+ // Closes the window synchronously. Note that this should not be called from
+ // an ATL message callback as it deletes the WidgetWin and ATL will
+ // dereference it after the callback is processed.
+ void CloseNow();
+
+ // All classes registered by WidgetWin 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)
+
+ // Reflected message handler
+ MESSAGE_HANDLER_EX(kReflectedMessage, OnReflectedMessage)
+
+ // CustomFrameWindow hacks
+ MESSAGE_HANDLER_EX(WM_NCUAHDRAWCAPTION, OnNCUAHDrawCaption)
+ MESSAGE_HANDLER_EX(WM_NCUAHDRAWFRAME, OnNCUAHDrawFrame)
+
+ // Vista and newer
+ MESSAGE_HANDLER_EX(WM_DWMCOMPOSITIONCHANGED, OnDwmCompositionChanged)
+
+ // Non-atlcrack.h handlers
+ MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject)
+ MESSAGE_HANDLER_EX(WM_NCMOUSELEAVE, OnNCMouseLeave)
+ MESSAGE_HANDLER_EX(WM_MOUSELEAVE, OnMouseLeave)
+ MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel)
+
+ // This list is in _ALPHABETICAL_ order! OR I WILL HURT YOU.
+ MSG_WM_ACTIVATE(OnActivate)
+ MSG_WM_ACTIVATEAPP(OnActivateApp)
+ MSG_WM_APPCOMMAND(OnAppCommand)
+ 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_ENDSESSION(OnEndSession)
+ MSG_WM_ENTERSIZEMOVE(OnEnterSizeMove)
+ MSG_WM_EXITMENULOOP(OnExitMenuLoop)
+ MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo)
+ MSG_WM_HSCROLL(OnHScroll)
+ MSG_WM_INITMENU(OnInitMenu)
+ MSG_WM_INITMENUPOPUP(OnInitMenuPopup)
+ MSG_WM_KEYDOWN(OnKeyDown)
+ MSG_WM_KEYUP(OnKeyUp)
+ MSG_WM_SYSKEYDOWN(OnKeyDown)
+ MSG_WM_SYSKEYUP(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_MOUSEMOVE(OnMouseMove)
+ MSG_WM_MOVE(OnMove)
+ 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_NCMBUTTONDBLCLK(OnNCMButtonDblClk)
+ MSG_WM_NCMBUTTONDOWN(OnNCMButtonDown)
+ MSG_WM_NCMBUTTONUP(OnNCMButtonUp)
+ 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_POWERBROADCAST(OnPowerBroadcast)
+ MSG_WM_RBUTTONDBLCLK(OnRButtonDblClk)
+ MSG_WM_RBUTTONDOWN(OnRButtonDown)
+ MSG_WM_RBUTTONUP(OnRButtonUp)
+ MSG_WM_SETCURSOR(OnSetCursor)
+ MSG_WM_SETFOCUS(OnSetFocus)
+ MSG_WM_SETICON(OnSetIcon)
+ MSG_WM_SETTEXT(OnSetText)
+ MSG_WM_SETTINGCHANGE(OnSettingChange)
+ MSG_WM_SIZE(OnSize)
+ MSG_WM_SYSCOMMAND(OnSysCommand)
+ MSG_WM_THEMECHANGED(OnThemeChanged)
+ MSG_WM_VSCROLL(OnVScroll)
+ MSG_WM_WINDOWPOSCHANGING(OnWindowPosChanging)
+ MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged)
+ END_MSG_MAP()
+
+ // Overridden from Widget:
+ virtual void GetBounds(gfx::Rect* out, bool including_frame) const;
+ virtual gfx::NativeView GetNativeView() const;
+ virtual void PaintNow(const gfx::Rect& update_rect);
+ virtual RootView* GetRootView();
+ virtual bool IsVisible() const;
+ virtual bool IsActive() const;
+ virtual TooltipManager* GetTooltipManager();
+ virtual Window* GetWindow();
+ virtual const Window* GetWindow() const;
+
+ // 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();
+
+ // Overridden from AcceleratorTarget:
+ virtual bool AcceleratorPressed(const Accelerator& accelerator) {
+ return false;
+ }
+
+ void SetFocusTraversableParent(FocusTraversable* parent);
+ void SetFocusTraversableParentView(View* parent_view);
+
+ virtual bool GetAccelerator(int cmd_id, Accelerator* accelerator) {
+ return false;
+ }
+
+ BOOL IsWindow() const {
+ return ::IsWindow(GetNativeView());
+ }
+
+ BOOL ShowWindow(int command) {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::ShowWindow(GetNativeView(), command);
+ }
+
+ HWND SetCapture() {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::SetCapture(GetNativeView());
+ }
+
+ HWND GetParent() const {
+ return ::GetParent(GetNativeView());
+ }
+
+ LONG GetWindowLong(int index) {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::GetWindowLong(GetNativeView(), index);
+ }
+
+ BOOL GetWindowRect(RECT* rect) const {
+ return ::GetWindowRect(GetNativeView(), rect);
+ }
+
+ LONG SetWindowLong(int index, LONG new_long) {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::SetWindowLong(GetNativeView(), index, new_long);
+ }
+
+ BOOL SetWindowPos(HWND hwnd_after, int x, int y, int cx, int cy, UINT flags) {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::SetWindowPos(GetNativeView(), hwnd_after, x, y, cx, cy, flags);
+ }
+
+ BOOL IsZoomed() const {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::IsZoomed(GetNativeView());
+ }
+
+ 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(GetNativeView()));
+ return ::MoveWindow(GetNativeView(), x, y, width, height, repaint);
+ }
+
+ int SetWindowRgn(HRGN region, BOOL redraw) {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::SetWindowRgn(GetNativeView(), region, redraw);
+ }
+
+ BOOL GetClientRect(RECT* rect) const {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::GetClientRect(GetNativeView(), rect);
+ }
+
+ protected:
+
+ // Call close instead of this to Destroy the window.
+ BOOL DestroyWindow() {
+ DCHECK(::IsWindow(GetNativeView()));
+ return ::DestroyWindow(GetNativeView());
+ }
+
+ // Message Handlers
+ // These are all virtual so that specialized Widgets 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) {
+ SetMsgHandled(FALSE);
+ }
+ virtual void OnActivateApp(BOOL active, DWORD thread_id) {
+ SetMsgHandled(FALSE);
+ }
+ virtual LRESULT OnAppCommand(HWND window, short app_command, WORD device,
+ int keystate) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual void OnCancelMode() {}
+ virtual void OnCaptureChanged(HWND hwnd);
+ virtual void OnClose();
+ virtual void OnCommand(UINT notification_code, int command_id, HWND window) {
+ SetMsgHandled(FALSE);
+ }
+ 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 OnDwmCompositionChanged(UINT msg,
+ WPARAM w_param,
+ LPARAM l_param) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual void OnEndSession(BOOL ending, UINT logoff) { SetMsgHandled(FALSE); }
+ virtual void OnEnterSizeMove() { SetMsgHandled(FALSE); }
+ virtual void OnExitMenuLoop(BOOL is_track_popup_menu) {
+ SetMsgHandled(FALSE);
+ }
+ virtual LRESULT OnEraseBkgnd(HDC dc);
+ virtual LRESULT OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param);
+ virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info) {
+ SetMsgHandled(FALSE);
+ }
+ virtual void OnHScroll(int scroll_type, short position, HWND scrollbar) {
+ SetMsgHandled(FALSE);
+ }
+ virtual void OnInitMenu(HMENU menu) { SetMsgHandled(FALSE); }
+ virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_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 LRESULT OnMouseLeave(UINT message, WPARAM w_param, LPARAM l_param);
+ virtual LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param);
+ virtual void OnMove(const CPoint& point) { SetMsgHandled(FALSE); }
+ virtual void OnMoving(UINT param, const LPRECT new_bounds) { }
+ 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 void OnNCMButtonDblClk(UINT flags, const CPoint& point);
+ virtual void OnNCMButtonDown(UINT flags, const CPoint& point);
+ virtual void OnNCMButtonUp(UINT flags, const CPoint& point);
+ virtual LRESULT OnNCMouseLeave(UINT uMsg, WPARAM w_param, LPARAM l_param);
+ 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 OnNCUAHDrawCaption(UINT msg,
+ WPARAM w_param,
+ LPARAM l_param) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual LRESULT OnNCUAHDrawFrame(UINT msg, WPARAM w_param, LPARAM l_param) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual LRESULT OnNotify(int w_param, NMHDR* l_param);
+ virtual void OnPaint(HDC dc);
+ virtual LRESULT OnPowerBroadcast(DWORD power_event, DWORD data) {
+ base::SystemMonitor* monitor = base::SystemMonitor::Get();
+ if (monitor)
+ monitor->ProcessWmPowerBroadcastMessage(power_event);
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ 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 OnSetIcon(UINT size_type, HICON new_icon) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual LRESULT OnSetText(const wchar_t* text) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+ virtual void OnSettingChange(UINT flags, const wchar_t* section) {
+ SetMsgHandled(FALSE);
+ }
+ virtual void OnSize(UINT param, const CSize& size);
+ virtual void OnSysCommand(UINT notification_code, CPoint click) { }
+ virtual void OnThemeChanged();
+ virtual void OnVScroll(int scroll_type, short position, HWND scrollbar) {
+ SetMsgHandled(FALSE);
+ }
+ virtual void OnWindowPosChanging(WINDOWPOS* window_pos) {
+ 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. |is_nonclient| is true when we should track WM_NCMOUSELEAVE
+ // messages instead of WM_MOUSELEAVE ones.
+ void TrackMouseEvents(DWORD mouse_tracking_flags);
+
+ // 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,
+ bool non_client);
+ void ProcessMouseDragged(const CPoint& point, UINT flags);
+ void ProcessMouseReleased(const CPoint& point, UINT flags);
+ void ProcessMouseMoved(const CPoint& point, UINT flags, bool is_nonclient);
+ void ProcessMouseExited();
+
+ // 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 WidgetWin 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_;
+
+ // Are a subclass of WindowWin?
+ bool is_window_;
+
+ private:
+ // Implementation of GetWindow. Ascends the parents of |hwnd| returning the
+ // first ancestor that is a Window.
+ static Window* GetWindowImpl(HWND hwnd);
+
+ // 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 WidgetWins.
+ 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 WidgetWin
+ // instance.
+ ScopedRunnableMethodFactory<WidgetWin> close_widget_factory_;
+
+ // The flags currently being used with TrackMouseEvent to track mouse
+ // messages. 0 if there is no active tracking. The value of this member is
+ // used when tracking is canceled.
+ DWORD active_mouse_tracking_flags_;
+
+ 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_;
+
+ // Should we keep an offscreen buffer? This is initially true and if the
+ // window has WS_EX_LAYERED then it remains true. You can set this to false
+ // at any time to ditch the buffer, and similarly set back to true to force
+ // creation of the buffer.
+ //
+ // NOTE: this is intended to be used with a layered window (a window with an
+ // extended window style of WS_EX_LAYERED). If you are using a layered window
+ // and NOT changing the layered alpha or anything else, then leave this value
+ // alone. OTOH if you are invoking SetLayeredWindowAttributes then you'll
+ // must likely want to set this to false, or after changing the alpha toggle
+ // the extended style bit to false than back to true. See MSDN for more
+ // details.
+ bool use_layered_buffer_;
+
+ // 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 screen coordinates.
+ int last_mouse_move_x_;
+ int last_mouse_move_y_;
+
+ // Instance of accessibility information and handling for MSAA root
+ CComPtr<IAccessible> accessibility_root_;
+
+ // Our hwnd.
+ HWND hwnd_;
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WIDGET_WIDGET_WIN_H_
diff --git a/views/window/client_view.cc b/views/window/client_view.cc
new file mode 100644
index 0000000..5b5afb1
--- /dev/null
+++ b/views/window/client_view.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "views/window/client_view.h"
+#include "views/window/window.h"
+#include "views/window/window_delegate.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// ClientView, public:
+
+ClientView::ClientView(Window* window, View* contents_view)
+ : window_(window),
+ contents_view_(contents_view) {
+}
+
+int ClientView::NonClientHitTest(const gfx::Point& point) {
+ return bounds().Contains(point) ? HTCLIENT : HTNOWHERE;
+}
+
+void ClientView::WindowClosing() {
+ window_->GetDelegate()->WindowClosing();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ClientView, View overrides:
+
+gfx::Size ClientView::GetPreferredSize() {
+ // |contents_view_| is allowed to be NULL up until the point where this view
+ // is attached to a Container.
+ if (contents_view_)
+ return contents_view_->GetPreferredSize();
+ return gfx::Size();
+}
+
+void ClientView::Layout() {
+ // |contents_view_| is allowed to be NULL up until the point where this view
+ // is attached to a Container.
+ if (contents_view_)
+ contents_view_->SetBounds(0, 0, width(), height());
+}
+
+void ClientView::ViewHierarchyChanged(bool is_add, View* parent, View* child) {
+ if (is_add && child == this) {
+ DCHECK(GetWidget());
+ DCHECK(contents_view_); // |contents_view_| must be valid now!
+ AddChildView(contents_view_);
+ }
+}
+
+void ClientView::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ // Overridden to do nothing. The NonClientView manually calls Layout on the
+ // ClientView when it is itself laid out, see comment in
+ // NonClientView::Layout.
+}
+
+} // namespace views
diff --git a/views/window/client_view.h b/views/window/client_view.h
new file mode 100644
index 0000000..b56fcfd
--- /dev/null
+++ b/views/window/client_view.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_CLIENT_VIEW_H_
+#define VIEWS_WINDOW_CLIENT_VIEW_H_
+
+#include "views/view.h"
+
+namespace views {
+
+class DialogClientView;
+class Window;
+
+///////////////////////////////////////////////////////////////////////////////
+// ClientView
+//
+// A ClientView is a View subclass that is used to occupy the "client area"
+// of a window. It provides basic information to the window that contains it
+// such as non-client hit testing information, sizing etc. Sub-classes of
+// ClientView are used to create more elaborate contents, e.g.
+// "DialogClientView".
+class ClientView : public View {
+ public:
+ // Constructs a ClientView object for the specified window with the specified
+ // contents. Since this object is created during the process of creating
+ // |window|, |contents_view| must be valid if you want the initial size of
+ // the window to be based on |contents_view|'s preferred size.
+ ClientView(Window* window, View* contents_view);
+ virtual ~ClientView() {}
+
+ // Manual RTTI ftw.
+ virtual DialogClientView* AsDialogClientView() { return NULL; }
+
+ // Returns true to signal that the Window can be closed. Specialized
+ // ClientView subclasses can override this default behavior to allow the
+ // close to be blocked until the user corrects mistakes, accepts a warning
+ // dialog, etc.
+ virtual bool CanClose() const { return true; }
+
+ // Notification that the window is closing. The default implementation
+ // forwards the notification to the delegate.
+ virtual void WindowClosing();
+
+ // Tests to see if the specified point (in view coordinates) is within the
+ // bounds of this view. If so, it returns HTCLIENT in this default
+ // implementation. If it is outside the bounds of this view, this must return
+ // HTNOWHERE to tell the caller to do further processing to determine where
+ // in the non-client area it is (if it is).
+ // Subclasses of ClientView can extend this logic by overriding this method
+ // to detect if regions within the client area count as parts of the "non-
+ // client" area. A good example of this is the size box at the bottom right
+ // corner of resizable dialog boxes.
+ virtual int NonClientHitTest(const gfx::Point& point);
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Layout();
+
+ protected:
+ // Overridden from View:
+ virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child);
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current);
+
+ // Accessors for private data members.
+ Window* window() const { return window_; }
+ void set_window(Window* window) { window_ = window; }
+ View* contents_view() const { return contents_view_; }
+ void set_contents_view(View* contents_view) {
+ contents_view_ = contents_view;
+ }
+
+ private:
+ // The Window that hosts this ClientView.
+ Window* window_;
+
+ // The View that this ClientView contains.
+ View* contents_view_;
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_CLIENT_VIEW_H_
diff --git a/views/window/custom_frame_view.cc b/views/window/custom_frame_view.cc
new file mode 100644
index 0000000..cf8173f
--- /dev/null
+++ b/views/window/custom_frame_view.cc
@@ -0,0 +1,695 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/custom_frame_view.h"
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/gfx/path.h"
+#include "app/resource_bundle.h"
+#include "base/win_util.h"
+#include "chrome/common/win_util.h"
+#include "grit/theme_resources.h"
+#include "views/window/client_view.h"
+#include "views/window/window_delegate.h"
+
+namespace views {
+
+// An enumeration of bitmap resources used by this window.
+enum {
+ 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 ActiveWindowResources : public WindowResources {
+ public:
+ ActiveWindowResources() {
+ InitClass();
+ }
+ virtual ~ActiveWindowResources() {
+ }
+
+ // WindowResources implementation:
+ virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const {
+ return standard_frame_bitmaps_[part];
+ }
+
+ 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);
+ }
+ initialized = true;
+ }
+ }
+
+ static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT];
+
+ 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];
+ }
+
+ 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);
+ }
+ initialized = true;
+ }
+ }
+
+ static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT];
+
+ DISALLOW_EVIL_CONSTRUCTORS(InactiveWindowResources);
+};
+
+// static
+SkBitmap* ActiveWindowResources::standard_frame_bitmaps_[];
+SkBitmap* InactiveWindowResources::standard_frame_bitmaps_[];
+
+// static
+WindowResources* CustomFrameView::active_resources_ = NULL;
+WindowResources* CustomFrameView::inactive_resources_ = NULL;
+ChromeFont* CustomFrameView::title_font_ = NULL;
+
+namespace {
+// The frame border is only visible in restored mode and is hardcoded to 4 px on
+// each side regardless of the system window border size.
+const int kFrameBorderThickness = 4;
+// Various edges of the frame border have a 1 px shadow along their edges; in a
+// few cases we shift elements based on this amount for visual appeal.
+const int kFrameShadowThickness = 1;
+// While resize areas on Windows are normally the same size as the window
+// borders, our top area is shrunk by 1 px to make it easier to move the window
+// around with our thinner top grabbable strip. (Incidentally, our side and
+// bottom resize areas don't match the frame border thickness either -- they
+// span the whole nonclient area, so there's no "dead zone" for the mouse.)
+const int kTopResizeAdjust = 1;
+// In the window corners, the resize areas don't actually expand bigger, but the
+// 16 px at the end of each edge triggers diagonal resizing.
+const int kResizeAreaCornerSize = 16;
+// The titlebar never shrinks to less than 18 px tall, plus the height of the
+// frame border and any bottom edge.
+const int kTitlebarMinimumHeight = 18;
+// The icon is inset 2 px from the left frame border.
+const int kIconLeftSpacing = 2;
+// The icon takes up 16/25th of the available titlebar height. (This is
+// expressed as two ints to avoid precision losses leading to off-by-one pixel
+// errors.)
+const int kIconHeightFractionNumerator = 16;
+const int kIconHeightFractionDenominator = 25;
+// The icon never shrinks below 16 px on a side.
+const int kIconMinimumSize = 16;
+// Because our frame border has a different "3D look" than Windows', with a less
+// cluttered top edge, we need to shift the icon up by 1 px in restored mode so
+// it looks more centered.
+const int kIconRestoredAdjust = 1;
+// There is a 4 px gap between the icon and the title text.
+const int kIconTitleSpacing = 4;
+// The title text starts 2 px below the bottom of the top frame border.
+const int kTitleTopSpacing = 2;
+// There is a 5 px gap between the title text and the caption buttons.
+const int kTitleCaptionSpacing = 5;
+// The caption buttons are always drawn 1 px down from the visible top of the
+// window (the true top in restored mode, or the top of the screen in maximized
+// mode).
+const int kCaptionTopSpacing = 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CustomFrameView, public:
+
+CustomFrameView::CustomFrameView(Window* frame)
+ : NonClientFrameView(),
+ close_button_(new ImageButton(this)),
+ restore_button_(new ImageButton(this)),
+ maximize_button_(new ImageButton(this)),
+ minimize_button_(new ImageButton(this)),
+ system_menu_button_(new ImageButton(this)),
+ should_show_minmax_buttons_(false),
+ frame_(frame) {
+ InitClass();
+ WindowResources* resources = active_resources_;
+
+ // Close button images will be set in LayoutWindowControls().
+ AddChildView(close_button_);
+
+ restore_button_->SetImage(CustomButton::BS_NORMAL,
+ resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON));
+ restore_button_->SetImage(CustomButton::BS_HOT,
+ resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_H));
+ restore_button_->SetImage(CustomButton::BS_PUSHED,
+ resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_P));
+ AddChildView(restore_button_);
+
+ maximize_button_->SetImage(CustomButton::BS_NORMAL,
+ resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON));
+ maximize_button_->SetImage(CustomButton::BS_HOT,
+ resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_H));
+ maximize_button_->SetImage(CustomButton::BS_PUSHED,
+ resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_P));
+ AddChildView(maximize_button_);
+
+ minimize_button_->SetImage(CustomButton::BS_NORMAL,
+ resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON));
+ minimize_button_->SetImage(CustomButton::BS_HOT,
+ resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_H));
+ minimize_button_->SetImage(CustomButton::BS_PUSHED,
+ resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_P));
+ AddChildView(minimize_button_);
+
+ should_show_minmax_buttons_ = frame_->GetDelegate()->CanMaximize();
+
+ AddChildView(system_menu_button_);
+}
+
+CustomFrameView::~CustomFrameView() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CustomFrameView, NonClientFrameView implementation:
+
+gfx::Rect CustomFrameView::GetBoundsForClientView() const {
+ return client_view_bounds_;
+}
+
+gfx::Rect CustomFrameView::GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const {
+ int top_height = NonClientTopBorderHeight();
+ int border_thickness = NonClientBorderThickness();
+ return gfx::Rect(std::max(0, client_bounds.x() - border_thickness),
+ std::max(0, client_bounds.y() - top_height),
+ client_bounds.width() + (2 * border_thickness),
+ client_bounds.height() + top_height + border_thickness);
+}
+
+gfx::Point CustomFrameView::GetSystemMenuPoint() const {
+ gfx::Point system_menu_point(
+ MirroredXCoordinateInsideView(FrameBorderThickness()),
+ NonClientTopBorderHeight() - BottomEdgeThicknessWithinNonClientHeight());
+ ConvertPointToScreen(this, &system_menu_point);
+ return system_menu_point;
+}
+
+int CustomFrameView::NonClientHitTest(const gfx::Point& point) {
+ // Then see if the point is within any of the window controls.
+ if (close_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(point))
+ return HTCLOSE;
+ if (restore_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(
+ point))
+ return HTMAXBUTTON;
+ if (maximize_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(
+ point))
+ return HTMAXBUTTON;
+ if (minimize_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(
+ point))
+ return HTMINBUTTON;
+ if (system_menu_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(
+ point))
+ return HTSYSMENU;
+
+ int window_component = GetHTComponentForFrame(point, FrameBorderThickness(),
+ NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize,
+ frame_->GetDelegate()->CanResize());
+ // Fall back to the caption if no other component matches.
+ return (window_component == HTNOWHERE) ? HTCAPTION : window_component;
+}
+
+void CustomFrameView::GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) {
+ DCHECK(window_mask);
+
+ if (frame_->IsMaximized())
+ return;
+
+ // Redefine the window visible region for the new size.
+ window_mask->moveTo(0, 3);
+ window_mask->lineTo(1, 2);
+ window_mask->lineTo(1, 1);
+ window_mask->lineTo(2, 1);
+ window_mask->lineTo(3, 0);
+
+ window_mask->lineTo(SkIntToScalar(size.width() - 3), 0);
+ window_mask->lineTo(SkIntToScalar(size.width() - 2), 1);
+ window_mask->lineTo(SkIntToScalar(size.width() - 1), 1);
+ window_mask->lineTo(SkIntToScalar(size.width() - 1), 2);
+ 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 CustomFrameView::EnableClose(bool enable) {
+ close_button_->SetEnabled(enable);
+}
+
+void CustomFrameView::ResetWindowControls() {
+ restore_button_->SetState(CustomButton::BS_NORMAL);
+ minimize_button_->SetState(CustomButton::BS_NORMAL);
+ maximize_button_->SetState(CustomButton::BS_NORMAL);
+ // The close button isn't affected by this constraint.
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CustomFrameView, View overrides:
+
+void CustomFrameView::Paint(ChromeCanvas* canvas) {
+ if (frame_->IsMaximized())
+ PaintMaximizedFrameBorder(canvas);
+ else
+ PaintRestoredFrameBorder(canvas);
+ PaintTitleBar(canvas);
+ if (!frame_->IsMaximized())
+ PaintRestoredClientEdge(canvas);
+}
+
+void CustomFrameView::Layout() {
+ LayoutWindowControls();
+ LayoutTitleBar();
+ LayoutClientView();
+}
+
+gfx::Size CustomFrameView::GetPreferredSize() {
+ gfx::Size pref = frame_->GetClientView()->GetPreferredSize();
+ gfx::Rect bounds(0, 0, pref.width(), pref.height());
+ return frame_->GetNonClientView()->GetWindowBoundsForClientBounds(
+ bounds).size();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CustomFrameView, ButtonListener implementation:
+
+void CustomFrameView::ButtonPressed(Button* sender) {
+ if (sender == close_button_)
+ frame_->Close();
+ else if (sender == minimize_button_)
+ frame_->Minimize();
+ else if (sender == maximize_button_)
+ frame_->Maximize();
+ else if (sender == restore_button_)
+ frame_->Restore();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CustomFrameView, private:
+
+int CustomFrameView::FrameBorderThickness() const {
+ return frame_->IsMaximized() ? 0 : kFrameBorderThickness;
+}
+
+int CustomFrameView::NonClientBorderThickness() const {
+ // In maximized mode, we don't show a client edge.
+ return FrameBorderThickness() +
+ (frame_->IsMaximized() ? 0 : kClientEdgeThickness);
+}
+
+int CustomFrameView::NonClientTopBorderHeight() const {
+ int title_top_spacing, title_thickness;
+ return TitleCoordinates(&title_top_spacing, &title_thickness);
+}
+
+int CustomFrameView::BottomEdgeThicknessWithinNonClientHeight() const {
+ return kFrameShadowThickness +
+ (frame_->IsMaximized() ? 0 : kClientEdgeThickness);
+}
+
+int CustomFrameView::TitleCoordinates(int* title_top_spacing,
+ int* title_thickness) const {
+ int frame_thickness = FrameBorderThickness();
+ int min_titlebar_height = kTitlebarMinimumHeight + frame_thickness;
+ *title_top_spacing = frame_thickness + kTitleTopSpacing;
+ // The bottom spacing should be the same apparent height as the top spacing.
+ // Because the actual top spacing height varies based on the system border
+ // thickness, we calculate this based on the restored top spacing and then
+ // adjust for maximized mode. We also don't include the frame shadow here,
+ // since while it's part of the bottom spacing it will be added in at the end.
+ int title_bottom_spacing =
+ kFrameBorderThickness + kTitleTopSpacing - kFrameShadowThickness;
+ if (frame_->IsMaximized()) {
+ // When we maximize, the top border appears to be chopped off; shift the
+ // title down to stay centered within the remaining space.
+ int title_adjust = (kFrameBorderThickness / 2);
+ *title_top_spacing += title_adjust;
+ title_bottom_spacing -= title_adjust;
+ }
+ *title_thickness = std::max(title_font_->height(),
+ min_titlebar_height - *title_top_spacing - title_bottom_spacing);
+ return *title_top_spacing + *title_thickness + title_bottom_spacing +
+ BottomEdgeThicknessWithinNonClientHeight();
+}
+
+void CustomFrameView::PaintRestoredFrameBorder(ChromeCanvas* canvas) {
+ 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.
+ canvas->TileImageInt(*right_edge, width() - right_edge->width(),
+ top_right_corner->height(), right_edge->width(),
+ height() - top_right_corner->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.
+ canvas->TileImageInt(*left_edge, 0, top_left_corner->height(),
+ left_edge->width(),
+ height() - top_left_corner->height() - bottom_left_corner->height());
+}
+
+void CustomFrameView::PaintMaximizedFrameBorder(
+ ChromeCanvas* canvas) {
+ SkBitmap* top_edge = resources()->GetPartBitmap(FRAME_TOP_EDGE);
+ canvas->TileImageInt(*top_edge, 0, FrameBorderThickness(), width(),
+ top_edge->height());
+
+ // The bottom of the titlebar actually comes from the top of the Client Edge
+ // graphic, with the actual client edge clipped off the bottom.
+ SkBitmap* titlebar_bottom = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP);
+ int edge_height = titlebar_bottom->height() - kClientEdgeThickness;
+ canvas->TileImageInt(*titlebar_bottom, 0,
+ frame_->GetClientView()->y() - edge_height, width(), edge_height);
+}
+
+void CustomFrameView::PaintTitleBar(ChromeCanvas* canvas) {
+ WindowDelegate* d = frame_->GetDelegate();
+
+ // It seems like in some conditions we can be asked to paint after the window
+ // that contains us is WM_DESTROYed. At this point, our delegate is NULL. The
+ // correct long term fix may be to shut down the RootView in WM_DESTROY.
+ if (!d)
+ return;
+
+ canvas->DrawStringInt(d->GetWindowTitle(), *title_font_, SK_ColorWHITE,
+ MirroredLeftPointForRect(title_bounds_), title_bounds_.y(),
+ title_bounds_.width(), title_bounds_.height());
+}
+
+void CustomFrameView::PaintRestoredClientEdge(ChromeCanvas* canvas) {
+ gfx::Rect client_area_bounds = frame_->GetClientView()->bounds();
+ int client_area_top = client_area_bounds.y();
+
+ 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);
+
+ // Top.
+ // This next calculation is necessary because the top center bitmap is shorter
+ // than the top left and right bitmaps. We need their top edges to line up,
+ // and we need the left and right edges to start below the corners' bottoms.
+ int top_edge_y = client_area_top - top->height();
+ client_area_top = top_edge_y + top_left->height();
+ canvas->DrawBitmapInt(*top_left, client_area_bounds.x() - top_left->width(),
+ top_edge_y);
+ canvas->TileImageInt(*top, client_area_bounds.x(), top_edge_y,
+ client_area_bounds.width(), top->height());
+ canvas->DrawBitmapInt(*top_right, client_area_bounds.right(), top_edge_y);
+
+ // Right.
+ int client_area_bottom =
+ std::max(client_area_top, client_area_bounds.bottom());
+ int client_area_height = client_area_bottom - client_area_top;
+ canvas->TileImageInt(*right, client_area_bounds.right(), client_area_top,
+ right->width(), client_area_height);
+
+ // Bottom.
+ canvas->DrawBitmapInt(*bottom_right, client_area_bounds.right(),
+ client_area_bottom);
+ canvas->TileImageInt(*bottom, client_area_bounds.x(), client_area_bottom,
+ client_area_bounds.width(), bottom_right->height());
+ canvas->DrawBitmapInt(*bottom_left,
+ client_area_bounds.x() - bottom_left->width(), client_area_bottom);
+
+ // Left.
+ canvas->TileImageInt(*left, client_area_bounds.x() - left->width(),
+ client_area_top, left->width(), client_area_height);
+}
+
+void CustomFrameView::LayoutWindowControls() {
+ close_button_->SetImageAlignment(ImageButton::ALIGN_LEFT,
+ ImageButton::ALIGN_BOTTOM);
+ // Maximized buttons start at window top so that even if their images aren't
+ // drawn flush with the screen edge, they still obey Fitts' Law.
+ bool is_maximized = frame_->IsMaximized();
+ int frame_thickness = FrameBorderThickness();
+ int caption_y = is_maximized ? frame_thickness : kCaptionTopSpacing;
+ int top_extra_height = is_maximized ? kCaptionTopSpacing : 0;
+ // There should always be the same number of non-shadow pixels visible to the
+ // side of the caption buttons. In maximized mode we extend the rightmost
+ // button to the screen corner to obey Fitts' Law.
+ int right_extra_width = is_maximized ?
+ (kFrameBorderThickness - kFrameShadowThickness) : 0;
+ gfx::Size close_button_size = close_button_->GetPreferredSize();
+ close_button_->SetBounds(width() - close_button_size.width() -
+ right_extra_width - frame_thickness, caption_y,
+ close_button_size.width() + right_extra_width,
+ close_button_size.height() + top_extra_height);
+
+ // When the window is restored, we show a maximized button; otherwise, we show
+ // a restore button.
+ bool is_restored = !is_maximized && !frame_->IsMinimized();
+ views::ImageButton* invisible_button = is_restored ?
+ restore_button_ : maximize_button_;
+ invisible_button->SetVisible(false);
+
+ views::ImageButton* visible_button = is_restored ?
+ maximize_button_ : restore_button_;
+ FramePartBitmap normal_part, hot_part, pushed_part;
+ if (should_show_minmax_buttons_) {
+ visible_button->SetVisible(true);
+ visible_button->SetImageAlignment(ImageButton::ALIGN_LEFT,
+ ImageButton::ALIGN_BOTTOM);
+ gfx::Size visible_button_size = visible_button->GetPreferredSize();
+ visible_button->SetBounds(close_button_->x() - visible_button_size.width(),
+ caption_y, visible_button_size.width(),
+ visible_button_size.height() + top_extra_height);
+
+ minimize_button_->SetVisible(true);
+ minimize_button_->SetImageAlignment(ImageButton::ALIGN_LEFT,
+ ImageButton::ALIGN_BOTTOM);
+ gfx::Size minimize_button_size = minimize_button_->GetPreferredSize();
+ minimize_button_->SetBounds(
+ visible_button->x() - minimize_button_size.width(), caption_y,
+ minimize_button_size.width(),
+ minimize_button_size.height() + top_extra_height);
+
+ normal_part = FRAME_CLOSE_BUTTON_ICON;
+ hot_part = FRAME_CLOSE_BUTTON_ICON_H;
+ pushed_part = FRAME_CLOSE_BUTTON_ICON_P;
+ } else {
+ visible_button->SetVisible(false);
+ minimize_button_->SetVisible(false);
+
+ normal_part = FRAME_CLOSE_BUTTON_ICON_SA;
+ hot_part = FRAME_CLOSE_BUTTON_ICON_SA_H;
+ pushed_part = FRAME_CLOSE_BUTTON_ICON_SA_P;
+ }
+
+ close_button_->SetImage(CustomButton::BS_NORMAL,
+ active_resources_->GetPartBitmap(normal_part));
+ close_button_->SetImage(CustomButton::BS_HOT,
+ active_resources_->GetPartBitmap(hot_part));
+ close_button_->SetImage(CustomButton::BS_PUSHED,
+ active_resources_->GetPartBitmap(pushed_part));
+}
+
+void CustomFrameView::LayoutTitleBar() {
+ // Always lay out the icon, even when it's not present, so we can lay out the
+ // window title based on its position.
+ int frame_thickness = FrameBorderThickness();
+ int icon_x = frame_thickness + kIconLeftSpacing;
+
+ // The usable height of the titlebar area is the total height minus the top
+ // resize border and any edge area we draw at its bottom.
+ int title_top_spacing, title_thickness;
+ int top_height = TitleCoordinates(&title_top_spacing, &title_thickness);
+ int available_height = top_height - frame_thickness -
+ BottomEdgeThicknessWithinNonClientHeight();
+
+ // The icon takes up a constant fraction of the available height, down to a
+ // minimum size, and is always an even number of pixels on a side (presumably
+ // to make scaled icons look better). It's centered within the usable height.
+ int icon_size = std::max((available_height * kIconHeightFractionNumerator /
+ kIconHeightFractionDenominator) / 2 * 2, kIconMinimumSize);
+ int icon_y = ((available_height - icon_size) / 2) + frame_thickness;
+
+ // Hack: Our frame border has a different "3D look" than Windows'. Theirs has
+ // a more complex gradient on the top that they push their icon/title below;
+ // then the maximized window cuts this off and the icon/title are centered in
+ // the remaining space. Because the apparent shape of our border is simpler,
+ // using the same positioning makes things look slightly uncentered with
+ // restored windows, so we come up to compensate.
+ if (!frame_->IsMaximized())
+ icon_y -= kIconRestoredAdjust;
+
+ views::WindowDelegate* d = frame_->GetDelegate();
+ if (!d->ShouldShowWindowIcon())
+ icon_size = 0;
+ system_menu_button_->SetBounds(icon_x, icon_y, icon_size, icon_size);
+
+ // Size the title.
+ int icon_right = icon_x + icon_size;
+ int title_x =
+ icon_right + (d->ShouldShowWindowIcon() ? kIconTitleSpacing : 0);
+ int title_right = (should_show_minmax_buttons_ ?
+ minimize_button_->x() : close_button_->x()) - kTitleCaptionSpacing;
+ title_bounds_.SetRect(title_x,
+ title_top_spacing + ((title_thickness - title_font_->height()) / 2),
+ std::max(0, title_right - title_x), title_font_->height());
+}
+
+void CustomFrameView::LayoutClientView() {
+ int top_height = NonClientTopBorderHeight();
+ int border_thickness = NonClientBorderThickness();
+ client_view_bounds_.SetRect(
+ border_thickness,
+ top_height,
+ std::max(0, width() - (2 * border_thickness)),
+ std::max(0, height() - top_height - border_thickness));
+}
+
+// static
+void CustomFrameView::InitClass() {
+ static bool initialized = false;
+ if (!initialized) {
+ active_resources_ = new ActiveWindowResources;
+ inactive_resources_ = new InactiveWindowResources;
+
+ title_font_ = new ChromeFont(win_util::GetWindowTitleFont());
+
+ initialized = true;
+ }
+}
+
+} // namespace views
diff --git a/views/window/custom_frame_view.h b/views/window/custom_frame_view.h
new file mode 100644
index 0000000..f071692
--- /dev/null
+++ b/views/window/custom_frame_view.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_
+#define VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_
+
+#include "views/controls/button/image_button.h"
+#include "views/window/non_client_view.h"
+#include "views/window/window.h"
+#include "views/window/window_resources.h"
+
+namespace gfx{
+class Size;
+class Path;
+class Point;
+}
+class ChromeCanvas;
+class ChromeFont;
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// CustomFrameView
+//
+// A ChromeView that provides the non client frame for Windows. This means
+// rendering the non-standard window caption, border, and controls.
+//
+////////////////////////////////////////////////////////////////////////////////
+class CustomFrameView : public NonClientFrameView,
+ public ButtonListener {
+ public:
+ explicit CustomFrameView(Window* frame);
+ virtual ~CustomFrameView();
+
+ // Overridden from views::NonClientFrameView:
+ virtual gfx::Rect GetBoundsForClientView() const;
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const;
+ virtual gfx::Point GetSystemMenuPoint() const;
+ virtual int NonClientHitTest(const gfx::Point& point);
+ virtual void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask);
+ virtual void EnableClose(bool enable);
+ virtual void ResetWindowControls();
+
+ // View overrides:
+ virtual void Paint(ChromeCanvas* canvas);
+ virtual void Layout();
+ virtual gfx::Size GetPreferredSize();
+
+ // ButtonListener implementation:
+ virtual void ButtonPressed(Button* sender);
+
+ private:
+ // Returns the thickness of the border that makes up the window frame edges.
+ // This does not include any client edge.
+ int FrameBorderThickness() const;
+
+ // Returns the thickness of the entire nonclient left, right, and bottom
+ // borders, including both the window frame and any client edge.
+ int NonClientBorderThickness() const;
+
+ // Returns the height of the entire nonclient top border, including the window
+ // frame, any title area, and any connected client edge.
+ int NonClientTopBorderHeight() const;
+
+ // A bottom border, and, in restored mode, a client edge are drawn at the
+ // bottom of the titlebar. This returns the total height drawn.
+ int BottomEdgeThicknessWithinNonClientHeight() const;
+
+ // Calculates multiple values related to title layout. Returns the height of
+ // the entire titlebar including any connected client edge.
+ int TitleCoordinates(int* title_top_spacing,
+ int* title_thickness) const;
+
+ // Paint various sub-components of this view.
+ void PaintRestoredFrameBorder(ChromeCanvas* canvas);
+ void PaintMaximizedFrameBorder(ChromeCanvas* canvas);
+ void PaintTitleBar(ChromeCanvas* canvas);
+ void PaintRestoredClientEdge(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 frame_->IsActive() || paint_as_active() ? active_resources_
+ : inactive_resources_;
+ }
+
+ // The bounds of the client view, in this view's coordinates.
+ gfx::Rect client_view_bounds_;
+
+ // The layout rect of the title, if visible.
+ gfx::Rect title_bounds_;
+
+ // Window controls.
+ ImageButton* close_button_;
+ ImageButton* restore_button_;
+ ImageButton* maximize_button_;
+ ImageButton* minimize_button_;
+ ImageButton* system_menu_button_; // Uses the window icon if visible.
+ bool should_show_minmax_buttons_;
+
+ // The window that owns this view.
+ Window* frame_;
+
+ // Initialize various static resources.
+ static void InitClass();
+ static WindowResources* active_resources_;
+ static WindowResources* inactive_resources_;
+ static ChromeFont* title_font_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CustomFrameView);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_
diff --git a/views/window/dialog_client_view.cc b/views/window/dialog_client_view.cc
new file mode 100644
index 0000000..b7989ac
--- /dev/null
+++ b/views/window/dialog_client_view.cc
@@ -0,0 +1,440 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/dialog_client_view.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <vsstyle.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/gfx/native_theme.h"
+#include "chrome/browser/views/standard_layout.h"
+#include "grit/generated_resources.h"
+#include "views/controls/button/native_button.h"
+#include "views/window/dialog_delegate.h"
+#include "views/window/window.h"
+
+namespace views {
+
+namespace {
+
+// Updates any of the standard buttons according to the delegate.
+void UpdateButtonHelper(NativeButton* button_view,
+ DialogDelegate* delegate,
+ MessageBoxFlags::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));
+}
+
+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->width(), view->height());
+}
+
+// DialogButton ----------------------------------------------------------------
+
+// DialogButtons is used for the ok/cancel buttons of the window. DialogButton
+// forwards AcceleratorPressed to the delegate.
+
+class DialogButton : public NativeButton {
+ public:
+ DialogButton(ButtonListener* listener,
+ Window* owner,
+ MessageBoxFlags::DialogButton type,
+ const std::wstring& title,
+ bool is_default)
+ : NativeButton(listener, title),
+ owner_(owner),
+ type_(type) {
+ SetIsDefault(is_default);
+ }
+
+ // Overridden to forward to the delegate.
+ virtual bool AcceleratorPressed(const Accelerator& accelerator) {
+ if (!owner_->GetDelegate()->AsDialogDelegate()->
+ AreAcceleratorsEnabled(type_)) {
+ return false;
+ }
+ return NativeButton::AcceleratorPressed(accelerator);
+ }
+
+ private:
+ Window* owner_;
+ const MessageBoxFlags::DialogButton type_;
+
+ DISALLOW_COPY_AND_ASSIGN(DialogButton);
+};
+
+} // namespace
+
+// static
+ChromeFont* DialogClientView::dialog_button_font_ = NULL;
+static const int kDialogMinButtonWidth = 75;
+static const int kDialogButtonLabelSpacing = 16;
+static const int kDialogButtonContentSpacing = 5;
+
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////
+// DialogClientView, public:
+
+DialogClientView::DialogClientView(Window* owner, View* contents_view)
+ : ClientView(owner, contents_view),
+ ok_button_(NULL),
+ cancel_button_(NULL),
+ extra_view_(NULL),
+ accepted_(false),
+ default_button_(NULL) {
+ InitClass();
+}
+
+DialogClientView::~DialogClientView() {
+}
+
+void DialogClientView::ShowDialogButtons() {
+ DialogDelegate* dd = GetDialogDelegate();
+ int buttons = dd->GetDialogButtons();
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_OK && !ok_button_) {
+ std::wstring label =
+ dd->GetDialogButtonLabel(MessageBoxFlags::DIALOGBUTTON_OK);
+ if (label.empty())
+ label = l10n_util::GetString(IDS_OK);
+ bool is_default_button =
+ (dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_OK) != 0;
+ ok_button_ = new DialogButton(this, window(),
+ MessageBoxFlags::DIALOGBUTTON_OK, label,
+ is_default_button);
+ ok_button_->SetGroup(kButtonGroup);
+ if (is_default_button)
+ default_button_ = ok_button_;
+ if (!(buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL))
+ ok_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false));
+ AddChildView(ok_button_);
+ }
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL && !cancel_button_) {
+ std::wstring label =
+ dd->GetDialogButtonLabel(MessageBoxFlags::DIALOGBUTTON_CANCEL);
+ if (label.empty()) {
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_OK) {
+ label = l10n_util::GetString(IDS_CANCEL);
+ } else {
+ label = l10n_util::GetString(IDS_CLOSE);
+ }
+ }
+ bool is_default_button =
+ (dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_CANCEL)
+ != 0;
+ cancel_button_ = new DialogButton(this, window(),
+ MessageBoxFlags::DIALOGBUTTON_CANCEL,
+ label, is_default_button);
+ cancel_button_->SetGroup(kButtonGroup);
+ cancel_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false));
+ if (is_default_button)
+ default_button_ = ok_button_;
+ AddChildView(cancel_button_);
+ }
+ 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));
+ }
+}
+
+void DialogClientView::SetDefaultButton(NativeButton* new_default_button) {
+ if (default_button_ && default_button_ != new_default_button) {
+ default_button_->SetIsDefault(false);
+ default_button_ = NULL;
+ }
+
+ if (new_default_button) {
+ default_button_ = new_default_button;
+ default_button_->SetIsDefault(true);
+ }
+}
+
+void DialogClientView::FocusWillChange(View* focused_before,
+ View* focused_now) {
+ NativeButton* new_default_button = NULL;
+ if (focused_now &&
+ focused_now->GetClassName() == NativeButton::kViewClassName) {
+ new_default_button = static_cast<NativeButton*>(focused_now);
+ } else {
+ // The focused view is not a button, get the default button from the
+ // delegate.
+ DialogDelegate* dd = GetDialogDelegate();
+ if ((dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_OK) != 0)
+ new_default_button = ok_button_;
+ if ((dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_CANCEL)
+ != 0)
+ new_default_button = cancel_button_;
+ }
+ SetDefaultButton(new_default_button);
+}
+
+// Changing dialog labels will change button widths.
+void DialogClientView::UpdateDialogButtons() {
+ DialogDelegate* dd = GetDialogDelegate();
+ int buttons = dd->GetDialogButtons();
+
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_OK)
+ UpdateButtonHelper(ok_button_, dd, MessageBoxFlags::DIALOGBUTTON_OK);
+
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL) {
+ UpdateButtonHelper(cancel_button_, dd,
+ MessageBoxFlags::DIALOGBUTTON_CANCEL);
+ }
+
+ LayoutDialogButtons();
+ SchedulePaint();
+}
+
+void DialogClientView::AcceptWindow() {
+ if (accepted_) {
+ // We should only get into AcceptWindow once.
+ NOTREACHED();
+ return;
+ }
+ if (GetDialogDelegate()->Accept(false)) {
+ accepted_ = true;
+ window()->Close();
+ }
+}
+
+void DialogClientView::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.
+ window()->Close();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DialogClientView, ClientView overrides:
+
+bool DialogClientView::CanClose() const {
+ if (!accepted_) {
+ DialogDelegate* dd = GetDialogDelegate();
+ int buttons = dd->GetDialogButtons();
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL)
+ return dd->Cancel();
+ if (buttons & MessageBoxFlags::DIALOGBUTTON_OK)
+ return dd->Accept(true);
+ }
+ return true;
+}
+
+void DialogClientView::WindowClosing() {
+ FocusManager* focus_manager = GetFocusManager();
+ DCHECK(focus_manager);
+ if (focus_manager)
+ focus_manager->RemoveFocusChangeListener(this);
+ ClientView::WindowClosing();
+}
+
+int DialogClientView::NonClientHitTest(const gfx::Point& point) {
+ if (size_box_bounds_.Contains(point.x() - x(), point.y() - y()))
+ return HTBOTTOMRIGHT;
+ return ClientView::NonClientHitTest(point);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DialogClientView, View overrides:
+
+void DialogClientView::Paint(ChromeCanvas* canvas) {
+ FillViewWithSysColor(canvas, this, GetSysColor(COLOR_3DFACE));
+}
+
+void DialogClientView::PaintChildren(ChromeCanvas* canvas) {
+ View::PaintChildren(canvas);
+ if (!window()->IsMaximized() && !window()->IsMinimized())
+ PaintSizeBox(canvas);
+}
+
+void DialogClientView::Layout() {
+ if (has_dialog_buttons())
+ LayoutDialogButtons();
+ LayoutContentsView();
+}
+
+void DialogClientView::ViewHierarchyChanged(bool is_add, View* parent,
+ View* child) {
+ if (is_add && child == this) {
+ // Can only add and update the dialog buttons _after_ they are added to the
+ // view hierarchy since they are native controls and require the
+ // Container's HWND.
+ ShowDialogButtons();
+ ClientView::ViewHierarchyChanged(is_add, parent, child);
+
+ FocusManager* focus_manager = GetFocusManager();
+ // Listen for focus change events so we can update the default button.
+ DCHECK(focus_manager); // bug #1291225: crash reports seem to indicate it
+ // can be NULL.
+ if (focus_manager)
+ focus_manager->AddFocusChangeListener(this);
+
+ // The "extra view" must be created and installed after the contents view
+ // has been inserted into the view hierarchy.
+ CreateExtraView();
+ UpdateDialogButtons();
+ Layout();
+ }
+}
+
+gfx::Size DialogClientView::GetPreferredSize() {
+ gfx::Size prefsize = contents_view()->GetPreferredSize();
+ int button_height = 0;
+ if (has_dialog_buttons()) {
+ if (cancel_button_)
+ button_height = cancel_button_->height();
+ else
+ button_height = ok_button_->height();
+ // Account for padding above and below the button.
+ button_height += kDialogButtonContentSpacing + kButtonVEdgeMargin;
+ }
+ prefsize.Enlarge(0, button_height);
+ return prefsize;
+}
+
+bool DialogClientView::AcceleratorPressed(const Accelerator& accelerator) {
+ DCHECK(accelerator.GetKeyCode() == VK_ESCAPE); // We only expect Escape key.
+ window()->Close();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DialogClientView, ButtonListener implementation:
+
+void DialogClientView::ButtonPressed(Button* sender) {
+ if (sender == ok_button_) {
+ AcceptWindow();
+ } else if (sender == cancel_button_) {
+ CancelWindow();
+ } else {
+ NOTREACHED();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DialogClientView, private:
+
+void DialogClientView::PaintSizeBox(ChromeCanvas* canvas) {
+ if (window()->GetDelegate()->CanResize() ||
+ window()->GetDelegate()->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...
+ size_box_bounds_ = GetLocalBounds(false);
+ size_box_bounds_.set_x(size_box_bounds_.right() - gripper_size.cx);
+ size_box_bounds_.set_y(size_box_bounds_.bottom() - gripper_size.cy);
+ RECT native_bounds = size_box_bounds_.ToRECT();
+ gfx::NativeTheme::instance()->PaintStatusGripper(
+ dc, SP_PANE, 1, 0, &native_bounds);
+ canvas->endPlatformPaint();
+ }
+}
+
+int DialogClientView::GetButtonWidth(int button) const {
+ DialogDelegate* dd = GetDialogDelegate();
+ std::wstring button_label = dd->GetDialogButtonLabel(
+ static_cast<MessageBoxFlags::DialogButton>(button));
+ int string_width = dialog_button_font_->GetStringWidth(button_label);
+ return std::max(string_width + kDialogButtonLabelSpacing,
+ kDialogMinButtonWidth);
+}
+
+int DialogClientView::GetButtonsHeight() const {
+ if (has_dialog_buttons()) {
+ if (cancel_button_)
+ return cancel_button_->height() + kDialogButtonContentSpacing;
+ return ok_button_->height() + kDialogButtonContentSpacing;
+ }
+ return 0;
+}
+
+void DialogClientView::LayoutDialogButtons() {
+ gfx::Rect extra_bounds;
+ if (cancel_button_) {
+ gfx::Size ps = cancel_button_->GetPreferredSize();
+ gfx::Rect lb = GetLocalBounds(false);
+ int button_width = GetButtonWidth(MessageBoxFlags::DIALOGBUTTON_CANCEL);
+ int button_x = lb.right() - button_width - kButtonHEdgeMargin;
+ int button_y = lb.bottom() - ps.height() - kButtonVEdgeMargin;
+ cancel_button_->SetBounds(button_x, button_y, button_width, ps.height());
+ // The extra view bounds are dependent on this button.
+ extra_bounds.set_width(std::max(0, cancel_button_->x()));
+ extra_bounds.set_y(cancel_button_->y());
+ }
+ if (ok_button_) {
+ gfx::Size ps = ok_button_->GetPreferredSize();
+ gfx::Rect lb = GetLocalBounds(false);
+ int button_width = GetButtonWidth(MessageBoxFlags::DIALOGBUTTON_OK);
+ int ok_button_right = lb.right() - kButtonHEdgeMargin;
+ if (cancel_button_)
+ ok_button_right = cancel_button_->x() - kRelatedButtonHSpacing;
+ int button_x = ok_button_right - button_width;
+ int button_y = lb.bottom() - ps.height() - kButtonVEdgeMargin;
+ ok_button_->SetBounds(button_x, button_y, ok_button_right - button_x,
+ ps.height());
+ // The extra view bounds are dependent on this button.
+ extra_bounds.set_width(std::max(0, ok_button_->x()));
+ extra_bounds.set_y(ok_button_->y());
+ }
+ if (extra_view_) {
+ gfx::Size ps = extra_view_->GetPreferredSize();
+ gfx::Rect lb = GetLocalBounds(false);
+ extra_bounds.set_x(lb.x() + kButtonHEdgeMargin);
+ extra_bounds.set_height(ps.height());
+ extra_view_->SetBounds(extra_bounds);
+ }
+}
+
+void DialogClientView::LayoutContentsView() {
+ gfx::Rect lb = GetLocalBounds(false);
+ lb.set_height(std::max(0, lb.height() - GetButtonsHeight()));
+ contents_view()->SetBounds(lb);
+ contents_view()->Layout();
+}
+
+void DialogClientView::CreateExtraView() {
+ View* extra_view = GetDialogDelegate()->GetExtraView();
+ if (extra_view && !extra_view_) {
+ extra_view_ = extra_view;
+ extra_view_->SetGroup(kButtonGroup);
+ AddChildView(extra_view_);
+ }
+}
+
+DialogDelegate* DialogClientView::GetDialogDelegate() const {
+ DialogDelegate* dd = window()->GetDelegate()->AsDialogDelegate();
+ DCHECK(dd);
+ return dd;
+}
+
+// static
+void DialogClientView::InitClass() {
+ static bool initialized = false;
+ if (!initialized) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ dialog_button_font_ = new ChromeFont(rb.GetFont(ResourceBundle::BaseFont));
+ initialized = true;
+ }
+}
+
+} // namespace views
diff --git a/views/window/dialog_client_view.h b/views/window/dialog_client_view.h
new file mode 100644
index 0000000..347b61c
--- /dev/null
+++ b/views/window/dialog_client_view.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_
+#define VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_
+
+#include "app/gfx/chrome_font.h"
+#include "views/focus/focus_manager.h"
+#include "views/controls/button/button.h"
+#include "views/window/client_view.h"
+
+namespace views {
+
+class DialogDelegate;
+class NativeButton;
+class Window;
+
+///////////////////////////////////////////////////////////////////////////////
+// DialogClientView
+//
+// This ClientView subclass provides the content of a typical dialog box,
+// including a strip of buttons at the bottom right of the window, default
+// accelerator handlers for accept and cancel, and the ability for the
+// embedded contents view to provide extra UI to be shown in the row of
+// buttons.
+//
+class DialogClientView : public ClientView,
+ public ButtonListener,
+ public FocusChangeListener {
+ public:
+ DialogClientView(Window* window, View* contents_view);
+ virtual ~DialogClientView();
+
+ // 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();
+
+ // Accept the changes made in the window that contains this ClientView.
+ void AcceptWindow();
+
+ // Cancel the changes made in the window that contains this ClientView.
+ void CancelWindow();
+
+ // Accessors in case the user wishes to adjust these buttons.
+ NativeButton* ok_button() const { return ok_button_; }
+ NativeButton* cancel_button() const { return cancel_button_; }
+
+ // Overridden from ClientView:
+ virtual bool CanClose() const;
+ virtual void WindowClosing();
+ virtual int NonClientHitTest(const gfx::Point& point);
+ virtual DialogClientView* AsDialogClientView() { return this; }
+
+ // FocusChangeListener implementation:
+ virtual void FocusWillChange(View* focused_before, View* focused_now);
+
+ protected:
+ // 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 gfx::Size GetPreferredSize();
+ virtual bool AcceleratorPressed(const Accelerator& accelerator);
+
+ // ButtonListener implementation:
+ virtual void ButtonPressed(Button* 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(int button) const;
+ int GetButtonsHeight() const;
+
+ // Position and size various sub-views.
+ void LayoutDialogButtons();
+ void LayoutContentsView();
+
+ // Makes the specified button the default button.
+ void SetDefaultButton(NativeButton* button);
+
+ bool has_dialog_buttons() const { return ok_button_ || cancel_button_; }
+
+ // Create and add the extra view, if supplied by the delegate.
+ void CreateExtraView();
+
+ // Returns the DialogDelegate for the window.
+ DialogDelegate* GetDialogDelegate() const;
+
+ // The dialog buttons.
+ NativeButton* ok_button_;
+ NativeButton* cancel_button_;
+
+ // The button that is currently the default button if any.
+ NativeButton* default_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_;
+
+ // True if the window was Accepted by the user using the OK button.
+ bool accepted_;
+
+ // Static resource initialization
+ static void InitClass();
+ static ChromeFont* dialog_button_font_;
+
+ DISALLOW_COPY_AND_ASSIGN(DialogClientView);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_
diff --git a/views/window/dialog_delegate.cc b/views/window/dialog_delegate.cc
new file mode 100644
index 0000000..4ae9a91
--- /dev/null
+++ b/views/window/dialog_delegate.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/dialog_delegate.h"
+
+#include "base/logging.h"
+#include "views/controls/button/native_button.h"
+#include "views/window/window.h"
+
+namespace views {
+
+// Overridden from WindowDelegate:
+
+int DialogDelegate::GetDefaultDialogButton() const {
+ if (GetDialogButtons() & MessageBoxFlags::DIALOGBUTTON_OK)
+ return MessageBoxFlags::DIALOGBUTTON_OK;
+ if (GetDialogButtons() & MessageBoxFlags::DIALOGBUTTON_CANCEL)
+ return MessageBoxFlags::DIALOGBUTTON_CANCEL;
+ return MessageBoxFlags::DIALOGBUTTON_NONE;
+}
+
+View* DialogDelegate::GetInitiallyFocusedView() {
+ // Focus the default button if any.
+ DialogClientView* dcv = GetDialogClientView();
+ int default_button = GetDefaultDialogButton();
+ if (default_button == MessageBoxFlags::DIALOGBUTTON_NONE)
+ return NULL;
+
+ if ((default_button & GetDialogButtons()) == 0) {
+ // The default button is a button we don't have.
+ NOTREACHED();
+ return NULL;
+ }
+
+ if (default_button & MessageBoxFlags::DIALOGBUTTON_OK)
+ return dcv->ok_button();
+ if (default_button & MessageBoxFlags::DIALOGBUTTON_CANCEL)
+ return dcv->cancel_button();
+ return NULL;
+}
+
+ClientView* DialogDelegate::CreateClientView(Window* window) {
+ return new DialogClientView(window, GetContentsView());
+}
+
+DialogClientView* DialogDelegate::GetDialogClientView() const {
+ ClientView* client_view = window()->GetClientView();
+ DialogClientView* dialog_client_view = client_view->AsDialogClientView();
+ DCHECK(dialog_client_view);
+ return dialog_client_view;
+}
+
+} // namespace views
diff --git a/views/window/dialog_delegate.h b/views/window/dialog_delegate.h
new file mode 100644
index 0000000..4418e61f
--- /dev/null
+++ b/views/window/dialog_delegate.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_DIALOG_DELEGATE_H_
+#define VIEWS_WINDOW_DIALOG_DELEGATE_H_
+
+#include "app/message_box_flags.h"
+#include "views/window/dialog_client_view.h"
+#include "views/window/window_delegate.h"
+
+namespace views {
+
+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; }
+
+ // 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 MessageBoxFlags::DIALOGBUTTON_OK |
+ MessageBoxFlags::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(MessageBoxFlags::DialogButton button) {
+ return true;
+ }
+
+ // Returns the label of the specified DialogButton.
+ virtual std::wstring GetDialogButtonLabel(
+ MessageBoxFlags::DialogButton button) const {
+ // empty string results in defaults for MessageBoxFlags::DIALOGBUTTON_OK,
+ // MessageBoxFlags::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 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
+ // MessageBoxFlags::DIALOGBUTTON_NONE if there is no default. Default
+ // behavior is to return MessageBoxFlags::DIALOGBUTTON_OK or
+ // MessageBoxFlags::DIALOGBUTTON_CANCEL (in that order) if they are
+ // present, MessageBoxFlags::DIALOGBUTTON_NONE otherwise.
+ virtual int GetDefaultDialogButton() const;
+
+ // Returns whether the specified dialog button is enabled.
+ virtual bool IsDialogButtonEnabled(
+ MessageBoxFlags::DialogButton button) const {
+ return true;
+ }
+
+ // Returns whether the specified dialog button is visible.
+ virtual bool IsDialogButtonVisible(
+ MessageBoxFlags::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; }
+
+ // Overridden from WindowDelegate:
+ virtual View* GetInitiallyFocusedView();
+
+ // Overridden from WindowDelegate:
+ virtual ClientView* CreateClientView(Window* window);
+
+ // A helper for accessing the DialogClientView object contained by this
+ // delegate's Window.
+ DialogClientView* GetDialogClientView() const;
+};
+
+} // namespace views
+
+#endif // VIEWS_WINDOW_DIALOG_DELEGATE_H_
diff --git a/views/window/native_frame_view.cc b/views/window/native_frame_view.cc
new file mode 100644
index 0000000..05a8dcb
--- /dev/null
+++ b/views/window/native_frame_view.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/native_frame_view.h"
+
+#include "views/window/window_win.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeFrameView, public:
+
+NativeFrameView::NativeFrameView(WindowWin* frame)
+ : NonClientFrameView(),
+ frame_(frame) {
+}
+
+NativeFrameView::~NativeFrameView() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeFrameView, NonClientFrameView overrides:
+
+gfx::Rect NativeFrameView::GetBoundsForClientView() const {
+ return gfx::Rect(0, 0, width(), height());
+}
+
+gfx::Rect NativeFrameView::GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const {
+ RECT rect = client_bounds.ToRECT();
+ AdjustWindowRectEx(&rect, frame_->window_style(), FALSE,
+ frame_->window_ex_style());
+ return gfx::Rect(rect);
+}
+
+gfx::Point NativeFrameView::GetSystemMenuPoint() const {
+ POINT temp = {0, -kFrameShadowThickness };
+ MapWindowPoints(frame_->GetNativeView(), HWND_DESKTOP, &temp, 1);
+ return gfx::Point(temp);
+}
+
+int NativeFrameView::NonClientHitTest(const gfx::Point& point) {
+ return HTNOWHERE;
+}
+
+void NativeFrameView::GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) {
+ // Nothing to do, we use the default window mask.
+}
+
+void NativeFrameView::EnableClose(bool enable) {
+ // Nothing to do, handled automatically by Window.
+}
+
+void NativeFrameView::ResetWindowControls() {
+ // Nothing to do.
+}
+
+} // namespace views
diff --git a/views/window/native_frame_view.h b/views/window/native_frame_view.h
new file mode 100644
index 0000000..e2efa9d
--- /dev/null
+++ b/views/window/native_frame_view.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_
+#define VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_
+
+#include "views/window/non_client_view.h"
+
+namespace views {
+
+class WindowWin;
+
+class NativeFrameView : public NonClientFrameView {
+ public:
+ explicit NativeFrameView(WindowWin* frame);
+ virtual ~NativeFrameView();
+
+ // NonClientFrameView overrides:
+ virtual gfx::Rect GetBoundsForClientView() const;
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const;
+ virtual gfx::Point GetSystemMenuPoint() const;
+ virtual int NonClientHitTest(const gfx::Point& point);
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask);
+ virtual void EnableClose(bool enable);
+ virtual void ResetWindowControls();
+
+ private:
+ // Our containing frame.
+ WindowWin* frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeFrameView);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_
diff --git a/views/window/non_client_view.cc b/views/window/non_client_view.cc
new file mode 100644
index 0000000..9a06cfc
--- /dev/null
+++ b/views/window/non_client_view.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/non_client_view.h"
+
+#include "chrome/common/win_util.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+#include "views/window/window.h"
+
+namespace views {
+
+const int NonClientFrameView::kFrameShadowThickness = 1;
+const int NonClientFrameView::kClientEdgeThickness = 1;
+
+// The frame view and the client view are always at these specific indices,
+// because the RootView message dispatch sends messages to items higher in the
+// z-order first and we always want the client view to have first crack at
+// handling mouse messages.
+static const int kFrameViewIndex = 0;
+static const int kClientViewIndex = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientView, public:
+
+NonClientView::NonClientView(Window* frame)
+ : frame_(frame),
+ client_view_(NULL),
+ use_native_frame_(win_util::ShouldUseVistaFrame()) {
+}
+
+NonClientView::~NonClientView() {
+ // This value may have been reset before the window hierarchy shuts down,
+ // so we need to manually remove it.
+ RemoveChildView(frame_view_.get());
+}
+
+void NonClientView::SetFrameView(NonClientFrameView* frame_view) {
+ // See comment in header about ownership.
+ frame_view->SetParentOwned(false);
+ if (frame_view_.get())
+ RemoveChildView(frame_view_.get());
+ frame_view_.reset(frame_view);
+ if (GetParent())
+ AddChildView(kFrameViewIndex, frame_view_.get());
+}
+
+bool NonClientView::CanClose() const {
+ return client_view_->CanClose();
+}
+
+void NonClientView::WindowClosing() {
+ client_view_->WindowClosing();
+}
+
+void NonClientView::SetUseNativeFrame(bool use_native_frame) {
+ use_native_frame_ = use_native_frame;
+ SetFrameView(frame_->CreateFrameViewForWindow());
+ GetRootView()->ThemeChanged();
+ Layout();
+ SchedulePaint();
+ frame_->UpdateFrameAfterFrameChange();
+}
+
+bool NonClientView::UseNativeFrame() const {
+ // The frame view may always require a custom frame, e.g. Constrained Windows.
+ bool always_use_custom_frame =
+ frame_view_.get() && frame_view_->AlwaysUseCustomFrame();
+ return !always_use_custom_frame && use_native_frame_;
+}
+
+void NonClientView::DisableInactiveRendering(bool disable) {
+ frame_view_->DisableInactiveRendering(disable);
+}
+
+gfx::Rect NonClientView::GetWindowBoundsForClientBounds(
+ const gfx::Rect client_bounds) const {
+ return frame_view_->GetWindowBoundsForClientBounds(client_bounds);
+}
+
+gfx::Point NonClientView::GetSystemMenuPoint() const {
+ return frame_view_->GetSystemMenuPoint();
+}
+
+int NonClientView::NonClientHitTest(const gfx::Point& point) {
+ // Sanity check.
+ if (!bounds().Contains(point))
+ return HTNOWHERE;
+
+ // The ClientView gets first crack, since it overlays the NonClientFrameView
+ // in the display stack.
+ int frame_component = client_view_->NonClientHitTest(point);
+ if (frame_component != HTNOWHERE)
+ return frame_component;
+
+ // Finally ask the NonClientFrameView. It's at the back of the display stack
+ // so it gets asked last.
+ return frame_view_->NonClientHitTest(point);
+}
+
+void NonClientView::GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) {
+ frame_view_->GetWindowMask(size, window_mask);
+}
+
+void NonClientView::EnableClose(bool enable) {
+ frame_view_->EnableClose(enable);
+}
+
+void NonClientView::ResetWindowControls() {
+ frame_view_->ResetWindowControls();
+}
+
+void NonClientView::LayoutFrameView() {
+ // First layout the NonClientFrameView, which determines the size of the
+ // ClientView...
+ frame_view_->SetBounds(0, 0, width(), height());
+
+ // We need to manually call Layout here because layout for the frame view can
+ // change independently of the bounds changing - e.g. after the initial
+ // display of the window the metrics of the native window controls can change,
+ // which does not change the bounds of the window but requires a re-layout to
+ // trigger a repaint. We override DidChangeBounds for the NonClientFrameView
+ // to do nothing so that SetBounds above doesn't cause Layout to be called
+ // twice.
+ frame_view_->Layout();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientView, View overrides:
+
+gfx::Size NonClientView::GetPreferredSize() {
+ // TODO(pkasting): This should probably be made to look similar to
+ // GetMinimumSize() below. This will require implementing GetPreferredSize()
+ // better in the various frame views.
+ gfx::Rect client_bounds(gfx::Point(), client_view_->GetPreferredSize());
+ return GetWindowBoundsForClientBounds(client_bounds).size();
+}
+
+gfx::Size NonClientView::GetMinimumSize() {
+ return frame_view_->GetMinimumSize();
+}
+
+void NonClientView::Layout() {
+ LayoutFrameView();
+
+ // Then layout the ClientView, using those bounds.
+ client_view_->SetBounds(frame_view_->GetBoundsForClientView());
+
+ // We need to manually call Layout on the ClientView as well for the same
+ // reason as above.
+ client_view_->Layout();
+}
+
+void NonClientView::ViewHierarchyChanged(bool is_add, View* parent,
+ View* child) {
+ // Add our two child views here as we are added to the Widget so that if we
+ // are subsequently resized all the parent-child relationships are
+ // established.
+ if (is_add && GetWidget() && child == this) {
+ AddChildView(kFrameViewIndex, frame_view_.get());
+ AddChildView(kClientViewIndex, client_view_);
+ }
+}
+
+views::View* NonClientView::GetViewForPoint(const gfx::Point& point) {
+ return GetViewForPoint(point, false);
+}
+
+views::View* NonClientView::GetViewForPoint(const gfx::Point& point,
+ bool can_create_floating) {
+ // Because of the z-ordering of our child views (the client view is positioned
+ // over the non-client frame view, if the client view ever overlaps the frame
+ // view visually (as it does for the browser window), then it will eat mouse
+ // events for the window controls. We override this method here so that we can
+ // detect this condition and re-route the events to the non-client frame view.
+ // The assumption is that the frame view's implementation of HitTest will only
+ // return true for area not occupied by the client view.
+ gfx::Point point_in_child_coords(point);
+ View::ConvertPointToView(this, frame_view_.get(), &point_in_child_coords);
+ if (frame_view_->HitTest(point_in_child_coords))
+ return frame_view_->GetViewForPoint(point);
+
+ return View::GetViewForPoint(point, can_create_floating);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientFrameView, View overrides:
+
+bool NonClientFrameView::HitTest(const gfx::Point& l) const {
+ // For the default case, we assume the non-client frame view never overlaps
+ // the client view.
+ return !GetWindow()->GetClientView()->bounds().Contains(l);
+}
+
+void NonClientFrameView::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ // Overridden to do nothing. The NonClientView manually calls Layout on the
+ // FrameView when it is itself laid out, see comment in NonClientView::Layout.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientFrameView, protected:
+
+int NonClientFrameView::GetHTComponentForFrame(const gfx::Point& point,
+ int top_resize_border_height,
+ int resize_border_thickness,
+ int top_resize_corner_height,
+ int resize_corner_width,
+ bool can_resize) {
+ // Tricky: In XP, native behavior is to return HTTOPLEFT and HTTOPRIGHT for
+ // a |resize_corner_size|-length strip of both the side and top borders, but
+ // only to return HTBOTTOMLEFT/HTBOTTOMRIGHT along the bottom border + corner
+ // (not the side border). Vista goes further and doesn't return these on any
+ // of the side borders. We allow callers to match either behavior.
+ int component;
+ if (point.x() < resize_border_thickness) {
+ if (point.y() < top_resize_corner_height)
+ component = HTTOPLEFT;
+ else if (point.y() >= (height() - resize_border_thickness))
+ component = HTBOTTOMLEFT;
+ else
+ component = HTLEFT;
+ } else if (point.x() >= (width() - resize_border_thickness)) {
+ if (point.y() < top_resize_corner_height)
+ component = HTTOPRIGHT;
+ else if (point.y() >= (height() - resize_border_thickness))
+ component = HTBOTTOMRIGHT;
+ else
+ component = HTRIGHT;
+ } else if (point.y() < top_resize_border_height) {
+ if (point.x() < resize_corner_width)
+ component = HTTOPLEFT;
+ else if (point.x() >= (width() - resize_corner_width))
+ component = HTTOPRIGHT;
+ else
+ component = HTTOP;
+ } else if (point.y() >= (height() - resize_border_thickness)) {
+ if (point.x() < resize_corner_width)
+ component = HTBOTTOMLEFT;
+ else if (point.x() >= (width() - resize_corner_width))
+ component = HTBOTTOMRIGHT;
+ else
+ component = HTBOTTOM;
+ } else {
+ return HTNOWHERE;
+ }
+
+ // If the window can't be resized, there are no resize boundaries, just
+ // window borders.
+ return can_resize ? component : HTBORDER;
+}
+
+} // namespace views
diff --git a/views/window/non_client_view.h b/views/window/non_client_view.h
new file mode 100644
index 0000000..d93f423
--- /dev/null
+++ b/views/window/non_client_view.h
@@ -0,0 +1,227 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_NON_CLIENT_VIEW_H_
+#define VIEWS_WINDOW_NON_CLIENT_VIEW_H_
+
+#include "base/task.h"
+#include "views/view.h"
+#include "views/window/client_view.h"
+
+namespace gfx {
+class Path;
+}
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientFrameView
+//
+// An object that subclasses NonClientFrameView is a View that renders and
+// responds to events within the frame portions of the non-client area of a
+// window. This view does _not_ contain the ClientView, but rather is a sibling
+// of it.
+class NonClientFrameView : public View {
+ public:
+ // Various edges of the frame border have a 1 px shadow along their edges; in
+ // a few cases we shift elements based on this amount for visual appeal.
+ static const int kFrameShadowThickness;
+ // In restored mode, we draw a 1 px edge around the content area inside the
+ // frame border.
+ static const int kClientEdgeThickness;
+
+ void DisableInactiveRendering(bool disable) {
+ paint_as_active_ = disable;
+ if (!paint_as_active_)
+ SchedulePaint();
+ }
+
+ // Returns the bounds (in this View's parent's coordinates) that the client
+ // view should be laid out within.
+ virtual gfx::Rect GetBoundsForClientView() const = 0;
+
+ // Returns true if this FrameView should always use the custom frame,
+ // regardless of the system settings. An example is the Constrained Window,
+ // which is a child window and must always provide its own frame.
+ virtual bool AlwaysUseCustomFrame() const { return false; }
+
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const = 0;
+ virtual gfx::Point GetSystemMenuPoint() const = 0;
+ virtual int NonClientHitTest(const gfx::Point& point) = 0;
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) = 0;
+ virtual void EnableClose(bool enable) = 0;
+ virtual void ResetWindowControls() = 0;
+
+ // Overridden from View:
+ virtual bool HitTest(const gfx::Point& l) const;
+
+ protected:
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current);
+
+ NonClientFrameView() : paint_as_active_(false) {}
+
+
+ // Helper for non-client view implementations to determine which area of the
+ // window border the specified |point| falls within. The other parameters are
+ // the size of the sizing edges, and whether or not the window can be
+ // resized.
+ int GetHTComponentForFrame(const gfx::Point& point,
+ int top_resize_border_height,
+ int resize_border_thickness,
+ int top_resize_corner_height,
+ int resize_corner_width,
+ bool can_resize);
+
+ // Accessor for paint_as_active_.
+ bool paint_as_active() const { return paint_as_active_; }
+
+ private:
+ // True when the non-client view should always be rendered as if the window
+ // were active, regardless of whether or not the top level window actually
+ // is active.
+ bool paint_as_active_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// NonClientView
+//
+// The NonClientView is the logical root of all Views contained within a
+// Window, except for the RootView which is its parent and of which it is the
+// sole child. The NonClientView has two children, the NonClientFrameView which
+// is responsible for painting and responding to events from the non-client
+// portions of the window, and the ClientView, which is responsible for the
+// same for the client area of the window:
+//
+// +- views::Window ------------------------------------+
+// | +- views::RootView ------------------------------+ |
+// | | +- views::NonClientView ---------------------+ | |
+// | | | +- views::NonClientView subclass ---+ | | |
+// | | | | | | | |
+// | | | | << all painting and event receiving >> | | | |
+// | | | | << of the non-client areas of a >> | | | |
+// | | | | << views::Window. >> | | | |
+// | | | | | | | |
+// | | | +----------------------------------------+ | | |
+// | | | +- views::ClientView or subclass --------+ | | |
+// | | | | | | | |
+// | | | | << all painting and event receiving >> | | | |
+// | | | | << of the client areas of a >> | | | |
+// | | | | << views::Window. >> | | | |
+// | | | | | | | |
+// | | | +----------------------------------------+ | | |
+// | | +--------------------------------------------+ | |
+// | +------------------------------------------------+ |
+// +----------------------------------------------------+
+//
+// The NonClientFrameView and ClientView are siblings because due to theme
+// changes the NonClientFrameView may be replaced with different
+// implementations (e.g. during the switch from DWM/Aero-Glass to Vista Basic/
+// Classic rendering).
+//
+class NonClientView : public View {
+ public:
+ explicit NonClientView(Window* frame);
+ virtual ~NonClientView();
+
+ // Replaces the current NonClientFrameView (if any) with the specified one.
+ void SetFrameView(NonClientFrameView* frame_view);
+
+ // Returns true if the ClientView determines that the containing window can be
+ // closed, false otherwise.
+ bool CanClose() const;
+
+ // Called by the containing Window when it is closed.
+ void WindowClosing();
+
+ // Changes the frame from native to custom depending on the value of
+ // |use_native_frame|.
+ void SetUseNativeFrame(bool use_native_frame);
+
+ // Returns true if the native window frame should be used, false if the
+ // NonClientView provides its own frame implementation.
+ bool UseNativeFrame() const;
+
+ // Prevents the window from being rendered as deactivated when |disable| is
+ // true, until called with |disable| false. Used when a sub-window is to be
+ // shown that shouldn't visually de-activate the window.
+ // Subclasses can override this to perform additional actions when this value
+ // changes.
+ void DisableInactiveRendering(bool disable);
+
+ // Returns the bounds of the window required to display the content area at
+ // the specified bounds.
+ gfx::Rect GetWindowBoundsForClientBounds(const gfx::Rect client_bounds) const;
+
+ // Returns the point, in screen coordinates, where the system menu should
+ // be shown so it shows up anchored to the system menu icon.
+ gfx::Point GetSystemMenuPoint() const;
+
+ // Determines the windows HT* code when the mouse cursor is at the
+ // specified point, in window coordinates.
+ int NonClientHitTest(const gfx::Point& point);
+
+ // 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.
+ void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask);
+
+ // Toggles the enable state for the Close button (and the Close menu item in
+ // the system menu).
+ void EnableClose(bool enable);
+
+ // Tells the window controls as rendered by the NonClientView to reset
+ // themselves to a normal state. This happens in situations where the
+ // containing window does not receive a normal sequences of messages that
+ // would lead to the controls returning to this normal state naturally, e.g.
+ // when the window is maximized, minimized or restored.
+ void ResetWindowControls();
+
+ // Get/Set client_view property.
+ ClientView* client_view() const { return client_view_; }
+ void set_client_view(ClientView* client_view) {
+ client_view_ = client_view;
+ }
+
+ // Layout just the frame view. This is necessary on Windows when non-client
+ // metrics such as the position of the window controls changes independently
+ // of a window resize message.
+ void LayoutFrameView();
+
+ // NonClientView, View overrides:
+ virtual gfx::Size GetPreferredSize();
+ virtual gfx::Size GetMinimumSize();
+ virtual void Layout();
+
+ protected:
+ // NonClientView, View overrides:
+ virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child);
+ virtual views::View* GetViewForPoint(const gfx::Point& point);
+ virtual views::View* GetViewForPoint(const gfx::Point& point,
+ bool can_create_floating);
+
+ private:
+ // The frame that hosts this NonClientView.
+ Window* frame_;
+
+ // A ClientView object or subclass, responsible for sizing the contents view
+ // of the window, hit testing and perhaps other tasks depending on the
+ // implementation.
+ ClientView* client_view_;
+
+ // The NonClientFrameView that renders the non-client portions of the window.
+ // This object is not owned by the view hierarchy because it can be replaced
+ // dynamically as the system settings change.
+ scoped_ptr<NonClientFrameView> frame_view_;
+
+ // Whether or not we should use the native frame.
+ bool use_native_frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(NonClientView);
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_NON_CLIENT_VIEW_H_
diff --git a/views/window/window.h b/views/window/window.h
new file mode 100644
index 0000000..17a2ff5
--- /dev/null
+++ b/views/window/window.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef VIEWS_WINDOW_WINDOW_H_
+#define VIEWS_WINDOW_WINDOW_H_
+
+#include "base/gfx/native_widget_types.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace views {
+
+class ClientView;
+class NonClientFrameView;
+class NonClientView;
+class WindowDelegate;
+
+// An interface implemented by an object that provides a top level window.
+class Window {
+ public:
+ virtual ~Window() {}
+
+ // Creates an instance of an object implementing this interface.
+ static Window* CreateChromeWindow(gfx::NativeWindow parent,
+ const gfx::Rect& bounds,
+ WindowDelegate* window_delegate);
+
+ // 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 int GetLocalizedContentsWidth(int col_resource_id);
+ static int GetLocalizedContentsHeight(int row_resource_id);
+ static gfx::Size GetLocalizedContentsSize(int col_resource_id,
+ int row_resource_id);
+
+ // Closes all windows that aren't identified as "app windows" via
+ // IsAppWindow. Called during application shutdown when the last "app window"
+ // is closed.
+ static void CloseAllSecondaryWindows();
+
+ // Retrieves the window's bounds, including its frame.
+ virtual gfx::Rect GetBounds() const = 0;
+
+ // Retrieves the restored bounds for the window.
+ virtual gfx::Rect GetNormalBounds() const = 0;
+
+ // Sizes and/or places the window to the specified bounds, size or position.
+ virtual void SetBounds(const gfx::Rect& bounds) = 0;
+
+ // As above, except the window is inserted after |other_window| in the window
+ // Z-order. If this window is not yet visible, other_window's monitor is used
+ // as the constraining rectangle, rather than this window's monitor.
+ virtual void SetBounds(const gfx::Rect& bounds,
+ gfx::NativeWindow other_window) = 0;
+
+ // Makes the window visible.
+ virtual void Show() = 0;
+
+ // Activate the window, assuming it already exists and is visible.
+ virtual void Activate() = 0;
+
+ // Closes the window, ultimately destroying it. This isn't immediate (it
+ // occurs after a return to the message loop. Implementors must also make sure
+ // that invoking Close multiple times doesn't cause bad things to happen,
+ // since it can happen.
+ virtual void Close() = 0;
+
+ // Maximizes/minimizes/restores the window.
+ virtual void Maximize() = 0;
+ virtual void Minimize() = 0;
+ virtual void Restore() = 0;
+
+ // Whether or not the window is currently active.
+ virtual bool IsActive() const = 0;
+
+ // Whether or not the window is currently visible.
+ virtual bool IsVisible() const = 0;
+
+ // Whether or not the window is maximized or minimized.
+ virtual bool IsMaximized() const = 0;
+ virtual bool IsMinimized() const = 0;
+
+ // Accessors for fullscreen state.
+ virtual void SetFullscreen(bool fullscreen) = 0;
+ virtual bool IsFullscreen() const = 0;
+
+ // Returns true if the Window is considered to be an "app window" - i.e.
+ // any window which when it is the last of its type closed causes the
+ // application to exit.
+ virtual bool IsAppWindow() const { return false; }
+
+ // Toggles the enable state for the Close button (and the Close menu item in
+ // the system menu).
+ virtual void EnableClose(bool enable) = 0;
+
+ // Prevents the window from being rendered as deactivated the next time it is.
+ // This state is reset automatically as soon as the window becomes activated
+ // again. There is no ability to control the state through this API as this
+ // leads to sync problems.
+ virtual void DisableInactiveRendering() = 0;
+
+ // Tell the window to update its title from the delegate.
+ virtual void UpdateWindowTitle() = 0;
+
+ // Tell the window to update its icon from the delegate.
+ virtual void UpdateWindowIcon() = 0;
+
+ // Creates an appropriate NonClientFrameView for this window.
+ virtual NonClientFrameView* CreateFrameViewForWindow() = 0;
+
+ // Updates the frame after an event caused it to be changed.
+ virtual void UpdateFrameAfterFrameChange() = 0;
+
+ // Retrieves the Window's delegate.
+ virtual WindowDelegate* GetDelegate() const = 0;
+
+ // Retrieves the Window's non-client view.
+ virtual NonClientView* GetNonClientView() const = 0;
+
+ // Retrieves the Window's client view.
+ virtual ClientView* GetClientView() const = 0;
+
+ // Retrieves the Window's native window handle.
+ virtual gfx::NativeWindow GetNativeWindow() const = 0;
+};
+
+} // namespace views
+
+#endif // #ifndef VIEWS_WINDOW_WINDOW_H_
diff --git a/views/window/window_delegate.cc b/views/window/window_delegate.cc
new file mode 100644
index 0000000..7b5f251
--- /dev/null
+++ b/views/window/window_delegate.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/window_delegate.h"
+
+// TODO(beng): hrmp. Fix this in http://crbug.com/4406
+#include "chrome/browser/browser_process.h"
+#include "chrome/common/pref_service.h"
+#include "views/window/client_view.h"
+#include "views/window/window.h"
+#include "skia/include/SkBitmap.h"
+
+namespace views {
+
+WindowDelegate::WindowDelegate() {
+}
+
+WindowDelegate::~WindowDelegate() {
+ ReleaseWindow();
+}
+
+// Returns the icon to be displayed in the window.
+SkBitmap WindowDelegate::GetWindowIcon() {
+ return SkBitmap();
+}
+
+void WindowDelegate::SaveWindowPlacement(const gfx::Rect& bounds,
+ bool maximized,
+ bool always_on_top) {
+ std::wstring window_name = GetWindowName();
+ if (window_name.empty() || !g_browser_process->local_state())
+ return;
+
+ DictionaryValue* window_preferences =
+ g_browser_process->local_state()->GetMutableDictionary(
+ window_name.c_str());
+ window_preferences->SetInteger(L"left", bounds.x());
+ window_preferences->SetInteger(L"top", bounds.y());
+ window_preferences->SetInteger(L"right", bounds.right());
+ window_preferences->SetInteger(L"bottom", bounds.bottom());
+ window_preferences->SetBoolean(L"maximized", maximized);
+ window_preferences->SetBoolean(L"always_on_top", always_on_top);
+}
+
+bool WindowDelegate::GetSavedWindowBounds(gfx::Rect* bounds) const {
+ std::wstring window_name = GetWindowName();
+ if (window_name.empty())
+ return false;
+
+ const DictionaryValue* dictionary =
+ g_browser_process->local_state()->GetDictionary(window_name.c_str());
+ int left, top, right, bottom;
+ if (!dictionary || !dictionary->GetInteger(L"left", &left) ||
+ !dictionary->GetInteger(L"top", &top) ||
+ !dictionary->GetInteger(L"right", &right) ||
+ !dictionary->GetInteger(L"bottom", &bottom))
+ return false;
+
+ bounds->SetRect(left, top, right - left, bottom - top);
+ return true;
+}
+
+bool WindowDelegate::GetSavedMaximizedState(bool* maximized) const {
+ std::wstring window_name = GetWindowName();
+ if (window_name.empty())
+ return false;
+
+ const DictionaryValue* dictionary =
+ g_browser_process->local_state()->GetDictionary(window_name.c_str());
+ return dictionary && dictionary->GetBoolean(L"maximized", maximized);
+}
+
+bool WindowDelegate::GetSavedAlwaysOnTopState(bool* always_on_top) const {
+ if (!g_browser_process->local_state())
+ return false;
+
+ std::wstring window_name = GetWindowName();
+ if (window_name.empty())
+ return false;
+
+ const DictionaryValue* dictionary =
+ g_browser_process->local_state()->GetDictionary(window_name.c_str());
+ return dictionary && dictionary->GetBoolean(L"always_on_top", always_on_top);
+}
+
+
+ClientView* WindowDelegate::CreateClientView(Window* window) {
+ return new ClientView(window, GetContentsView());
+}
+
+void WindowDelegate::ReleaseWindow() {
+ window_.release();
+}
+
+} // namespace views
diff --git a/views/window/window_delegate.h b/views/window/window_delegate.h
new file mode 100644
index 0000000..af8285c
--- /dev/null
+++ b/views/window/window_delegate.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_WINDOW_DELEGATE_H_
+#define VIEWS_WINDOW_WINDOW_DELEGATE_H_
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+
+class SkBitmap;
+
+namespace gfx {
+class Rect;
+}
+// TODO(maruel): Remove once gfx::Rect is used instead.
+namespace WTL {
+class CRect;
+}
+using WTL::CRect;
+
+namespace views {
+
+class ClientView;
+class DialogDelegate;
+class View;
+class Window;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 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:
+ WindowDelegate();
+ virtual ~WindowDelegate();
+
+ virtual DialogDelegate* AsDialogDelegate() { return NULL; }
+
+ // Returns true if the window can ever be resized.
+ virtual bool CanResize() const {
+ return false;
+ }
+
+ // Returns true if the window can ever 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() { 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();
+
+ // Returns true if a window icon should be shown.
+ virtual bool ShouldShowWindowIcon() const {
+ return false;
+ }
+
+ // Execute a command in the window's controller. Returns true if the command
+ // was handled, false if it was not.
+ virtual bool ExecuteWindowsCommand(int command_id) { return false; }
+
+ // Returns the window's name identifier. Used to identify this window for
+ // state restoration.
+ virtual std::wstring GetWindowName() const {
+ return std::wstring();
+ }
+
+ // Saves the window's bounds, maximized and always-on-top states. By default
+ // this uses the process' local state keyed by window name (See GetWindowName
+ // above). This behavior can be overridden to provide additional
+ // functionality.
+ virtual void SaveWindowPlacement(const gfx::Rect& bounds,
+ bool maximized,
+ bool always_on_top);
+
+ // Retrieves the window's bounds, maximized and always-on-top states. By
+ // default, this uses the process' local state keyed by window name (See
+ // GetWindowName above). This behavior can be overridden to provide
+ // additional functionality.
+ virtual bool GetSavedWindowBounds(gfx::Rect* bounds) const;
+ virtual bool GetSavedMaximizedState(bool* maximized) const;
+ virtual bool GetSavedAlwaysOnTopState(bool* always_on_top) const;
+
+ // Called when the window closes.
+ virtual void WindowClosing() { }
+
+ // Called when the window is destroyed. No events must be sent or received
+ // after this point. The delegate can use this opportunity to delete itself at
+ // this time if necessary.
+ virtual void DeleteDelegate() { }
+
+ // Returns the View that is contained within this Window.
+ virtual View* GetContentsView() {
+ return NULL;
+ }
+
+ // Called by the Window to create the Client View used to host the contents
+ // of the window.
+ virtual ClientView* CreateClientView(Window* window);
+
+ // An accessor to the Window this delegate is bound to.
+ Window* window() const { return window_.get(); }
+
+ protected:
+ // Releases the Window* we maintain. This should be done by a delegate in its
+ // WindowClosing handler if it intends to be re-cycled to be used on a
+ // different Window.
+ void ReleaseWindow();
+
+ private:
+ friend class WindowWin;
+ // This is a little unusual. We use a scoped_ptr here because it's
+ // initialized to NULL automatically. We do this because we want to allow
+ // people using this helper to not have to call a ctor on this object.
+ // Instead we just release the owning ref this pointer has when we are
+ // destroyed.
+ scoped_ptr<Window> window_;
+};
+
+} // namespace views
+
+#endif // VIEWS_WINDOW_WINDOW_DELEGATE_H_
diff --git a/views/window/window_resources.h b/views/window/window_resources.h
new file mode 100644
index 0000000..e79476e
--- /dev/null
+++ b/views/window/window_resources.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_WINDOW_RESOURCES_H_
+#define VIEWS_WINDOW_WINDOW_RESOURCES_H_
+
+class SkBitmap;
+
+namespace views {
+
+typedef int FramePartBitmap;
+
+///////////////////////////////////////////////////////////////////////////////
+// WindowResources
+//
+// An interface implemented by an object providing bitmaps to render the
+// contents of a window frame. The Window may swap in different
+// implementations of this interface to render different modes. The definition
+// of FramePartBitmap depends on the implementation.
+//
+class WindowResources {
+ public:
+ virtual ~WindowResources() { }
+ virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const = 0;
+};
+
+} // namespace views
+
+#endif // VIEWS_WINDOW_WINDOW_RESOURCES_H_
diff --git a/views/window/window_win.cc b/views/window/window_win.cc
new file mode 100644
index 0000000..f41ef18
--- /dev/null
+++ b/views/window/window_win.cc
@@ -0,0 +1,1446 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/window/window_win.h"
+
+#include <shellapi.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/gfx/icon_util.h"
+#include "app/gfx/path.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/win_util.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/common/win_util.h"
+#include "grit/generated_resources.h"
+#include "views/widget/root_view.h"
+#include "views/window/client_view.h"
+#include "views/window/custom_frame_view.h"
+#include "views/window/native_frame_view.h"
+#include "views/window/non_client_view.h"
+#include "views/window/window_delegate.h"
+
+namespace {
+
+bool GetMonitorAndRects(const RECT& rect,
+ HMONITOR* monitor,
+ gfx::Rect* monitor_rect,
+ gfx::Rect* work_area) {
+ DCHECK(monitor);
+ DCHECK(monitor_rect);
+ DCHECK(work_area);
+ *monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
+ if (!*monitor)
+ return false;
+ MONITORINFO monitor_info = { 0 };
+ monitor_info.cbSize = sizeof(monitor_info);
+ GetMonitorInfo(*monitor, &monitor_info);
+ *monitor_rect = monitor_info.rcMonitor;
+ *work_area = monitor_info.rcWork;
+ return true;
+}
+
+} // namespace
+
+namespace views {
+
+// A scoping class that prevents a window from being able to redraw in response
+// to invalidations that may occur within it for the lifetime of the object.
+//
+// Why would we want such a thing? Well, it turns out Windows has some
+// "unorthodox" behavior when it comes to painting its non-client areas.
+// Occasionally, Windows will paint portions of the default non-client area
+// right over the top of the custom frame. This is not simply fixed by handling
+// WM_NCPAINT/WM_PAINT, with some investigation it turns out that this
+// rendering is being done *inside* the default implementation of some message
+// handlers and functions:
+// . WM_SETTEXT
+// . WM_SETICON
+// . WM_NCLBUTTONDOWN
+// . EnableMenuItem, called from our WM_INITMENU handler
+// The solution is to handle these messages and call DefWindowProc ourselves,
+// but prevent the window from being able to update itself for the duration of
+// the call. We do this with this class, which automatically calls its
+// associated Window's lock and unlock functions as it is created and destroyed.
+// See documentation in those methods for the technique used.
+//
+// IMPORTANT: Do not use this scoping object for large scopes or periods of
+// time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh).
+//
+// I would love to hear Raymond Chen's explanation for all this. And maybe a
+// list of other messages that this applies to ;-)
+class WindowWin::ScopedRedrawLock {
+ public:
+ explicit ScopedRedrawLock(WindowWin* window) : window_(window) {
+ window_->LockUpdates();
+ }
+
+ ~ScopedRedrawLock() {
+ window_->UnlockUpdates();
+ }
+
+ private:
+ // The window having its style changed.
+ WindowWin* window_;
+};
+
+HCURSOR WindowWin::resize_cursors_[6];
+
+// 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;
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowWin, public:
+
+WindowWin::~WindowWin() {
+}
+
+// static
+Window* Window::CreateChromeWindow(gfx::NativeWindow parent,
+ const gfx::Rect& bounds,
+ WindowDelegate* window_delegate) {
+ WindowWin* window = new WindowWin(window_delegate);
+ window->GetNonClientView()->SetFrameView(window->CreateFrameViewForWindow());
+ window->Init(parent, bounds);
+ return window;
+}
+
+gfx::Rect WindowWin::GetBounds() const {
+ gfx::Rect bounds;
+ WidgetWin::GetBounds(&bounds, true);
+ return bounds;
+}
+
+gfx::Rect WindowWin::GetNormalBounds() const {
+ // If we're in fullscreen mode, we've changed the normal bounds to the monitor
+ // rect, so return the saved bounds instead.
+ if (IsFullscreen())
+ return gfx::Rect(saved_window_info_.window_rect);
+
+ WINDOWPLACEMENT wp;
+ wp.length = sizeof(wp);
+ const bool ret = !!GetWindowPlacement(GetNativeView(), &wp);
+ DCHECK(ret);
+ return gfx::Rect(wp.rcNormalPosition);
+}
+
+void WindowWin::SetBounds(const gfx::Rect& bounds) {
+ SetBounds(bounds, NULL);
+}
+
+void WindowWin::SetBounds(const gfx::Rect& bounds,
+ gfx::NativeWindow other_window) {
+ win_util::SetChildBounds(GetNativeView(), GetParent(), other_window, bounds,
+ kMonitorEdgePadding, 0);
+}
+
+void WindowWin::Show(int show_state) {
+ ShowWindow(show_state);
+ // When launched from certain programs like bash and Windows Live Messenger,
+ // show_state is set to SW_HIDE, so we need to correct that condition. We
+ // don't just change show_state to SW_SHOWNORMAL because MSDN says we must
+ // always first call ShowWindow with the specified value from STARTUPINFO,
+ // otherwise all future ShowWindow calls will be ignored (!!#@@#!). Instead,
+ // we call ShowWindow again in this case.
+ if (show_state == SW_HIDE) {
+ show_state = SW_SHOWNORMAL;
+ ShowWindow(show_state);
+ }
+
+ // We need to explicitly activate the window if we've been shown with a state
+ // that should activate, because if we're opened from a desktop shortcut while
+ // an existing window is already running it doesn't seem to be enough to use
+ // one of these flags to activate the window.
+ if (show_state == SW_SHOWNORMAL)
+ Activate();
+
+ SetInitialFocus();
+}
+
+int WindowWin::GetShowState() const {
+ return SW_SHOWNORMAL;
+}
+
+void WindowWin::ExecuteSystemMenuCommand(int command) {
+ if (command)
+ SendMessage(GetNativeView(), WM_SYSCOMMAND, command, 0);
+}
+
+void WindowWin::PushForceHidden() {
+ if (force_hidden_count_++ == 0)
+ Hide();
+}
+
+void WindowWin::PopForceHidden() {
+ if (--force_hidden_count_ <= 0) {
+ force_hidden_count_ = 0;
+ ShowWindow(SW_SHOW);
+ }
+}
+
+// static
+int Window::GetLocalizedContentsWidth(int col_resource_id) {
+ double chars = _wtof(l10n_util::GetString(col_resource_id).c_str());
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ ChromeFont font = rb.GetFont(ResourceBundle::BaseFont);
+ int width = font.GetExpectedTextWidth(static_cast<int>(chars));
+ DCHECK(width > 0);
+ return width;
+}
+
+// static
+int Window::GetLocalizedContentsHeight(int row_resource_id) {
+ double lines = _wtof(l10n_util::GetString(row_resource_id).c_str());
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ ChromeFont font = rb.GetFont(ResourceBundle::BaseFont);
+ int height = static_cast<int>(font.height() * lines);
+ DCHECK(height > 0);
+ return height;
+}
+
+// static
+gfx::Size Window::GetLocalizedContentsSize(int col_resource_id,
+ int row_resource_id) {
+ return gfx::Size(GetLocalizedContentsWidth(col_resource_id),
+ GetLocalizedContentsHeight(row_resource_id));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowWin, Window implementation:
+
+void WindowWin::Show() {
+ int show_state = GetShowState();
+ if (saved_maximized_state_)
+ show_state = SW_SHOWMAXIMIZED;
+ Show(show_state);
+}
+
+void WindowWin::Activate() {
+ if (IsMinimized())
+ ::ShowWindow(GetNativeView(), SW_RESTORE);
+ ::SetWindowPos(GetNativeView(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ SetForegroundWindow(GetNativeView());
+}
+
+void WindowWin::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;
+ }
+
+ if (non_client_view_->CanClose()) {
+ SaveWindowPosition();
+ RestoreEnabledIfNecessary();
+ WidgetWin::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_ && GetNativeView() == GetForegroundWindow() &&
+ IsWindowVisible(owning_hwnd_)) {
+ SetForegroundWindow(owning_hwnd_);
+ }
+ window_closed_ = true;
+ }
+}
+
+void WindowWin::Maximize() {
+ ExecuteSystemMenuCommand(SC_MAXIMIZE);
+}
+
+void WindowWin::Minimize() {
+ ExecuteSystemMenuCommand(SC_MINIMIZE);
+}
+
+void WindowWin::Restore() {
+ ExecuteSystemMenuCommand(SC_RESTORE);
+}
+
+bool WindowWin::IsActive() const {
+ return is_active_;
+}
+
+bool WindowWin::IsVisible() const {
+ return !!::IsWindowVisible(GetNativeView());
+}
+
+bool WindowWin::IsMaximized() const {
+ return !!::IsZoomed(GetNativeView());
+}
+
+bool WindowWin::IsMinimized() const {
+ return !!::IsIconic(GetNativeView());
+}
+
+void WindowWin::SetFullscreen(bool fullscreen) {
+ if (fullscreen_ == fullscreen)
+ return; // Nothing to do.
+
+ // Reduce jankiness during the following position changes by hiding the window
+ // until it's in the final position.
+ PushForceHidden();
+
+ // Size/position/style window appropriately.
+ if (!fullscreen_) {
+ // Save current window information. We force the window into restored mode
+ // before going fullscreen because Windows doesn't seem to hide the
+ // taskbar if the window is in the maximized state.
+ saved_window_info_.maximized = IsMaximized();
+ if (saved_window_info_.maximized)
+ Restore();
+ saved_window_info_.style = GetWindowLong(GWL_STYLE);
+ saved_window_info_.ex_style = GetWindowLong(GWL_EXSTYLE);
+ GetWindowRect(&saved_window_info_.window_rect);
+ }
+
+ // Toggle fullscreen mode.
+ fullscreen_ = fullscreen;
+
+ if (fullscreen_) {
+ // Set new window style and size.
+ MONITORINFO monitor_info;
+ monitor_info.cbSize = sizeof(monitor_info);
+ GetMonitorInfo(MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST),
+ &monitor_info);
+ gfx::Rect monitor_rect(monitor_info.rcMonitor);
+ SetWindowLong(GWL_STYLE,
+ saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
+ SetWindowLong(GWL_EXSTYLE,
+ saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME |
+ WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
+ SetWindowPos(NULL, monitor_rect.x(), monitor_rect.y(),
+ monitor_rect.width(), monitor_rect.height(),
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ } else {
+ // Reset original window style and size. The multiple window size/moves
+ // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be
+ // repainted. Better-looking methods welcome.
+ gfx::Rect new_rect(saved_window_info_.window_rect);
+ SetWindowLong(GWL_STYLE, saved_window_info_.style);
+ SetWindowLong(GWL_EXSTYLE, saved_window_info_.ex_style);
+ SetWindowPos(NULL, new_rect.x(), new_rect.y(), new_rect.width(),
+ new_rect.height(),
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ if (saved_window_info_.maximized)
+ Maximize();
+ }
+
+ // Undo our anti-jankiness hacks.
+ PopForceHidden();
+}
+
+bool WindowWin::IsFullscreen() const {
+ return fullscreen_;
+}
+
+void WindowWin::EnableClose(bool enable) {
+ // If the native frame is rendering its own close button, ask it to disable.
+ non_client_view_->EnableClose(enable);
+
+ // Disable the native frame's close button regardless of whether or not the
+ // native frame is in use, since this also affects the system menu.
+ EnableMenuItem(GetSystemMenu(GetNativeView(), 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 WindowWin::DisableInactiveRendering() {
+ disable_inactive_rendering_ = true;
+ non_client_view_->DisableInactiveRendering(disable_inactive_rendering_);
+}
+
+void WindowWin::UpdateWindowTitle() {
+ // If the non-client view is rendering its own title, it'll need to relayout
+ // now.
+ non_client_view_->Layout();
+
+ // Update the native frame's text. We do this regardless of whether or not
+ // the native frame is being used, since this also updates the taskbar, etc.
+ std::wstring window_title = window_delegate_->GetWindowTitle();
+ std::wstring localized_text;
+ if (l10n_util::AdjustStringForLocaleDirection(window_title, &localized_text))
+ window_title.assign(localized_text);
+ SetWindowText(GetNativeView(), window_title.c_str());
+}
+
+void WindowWin::UpdateWindowIcon() {
+ // If the non-client view is rendering its own icon, we need to tell it to
+ // repaint.
+ non_client_view_->SchedulePaint();
+
+ // Update the native frame's icon. We do this regardless of whether or not
+ // the native frame is being used, since this also updates the taskbar, etc.
+ SkBitmap icon = window_delegate_->GetWindowIcon();
+ if (!icon.isNull()) {
+ HICON windows_icon = IconUtil::CreateHICONFromSkBitmap(icon);
+ // We need to make sure to destroy the previous icon, otherwise we'll leak
+ // these GDI objects until we crash!
+ HICON old_icon = reinterpret_cast<HICON>(
+ SendMessage(GetNativeView(), WM_SETICON, ICON_SMALL,
+ reinterpret_cast<LPARAM>(windows_icon)));
+ if (old_icon)
+ DestroyIcon(old_icon);
+ old_icon = reinterpret_cast<HICON>(
+ SendMessage(GetNativeView(), WM_SETICON, ICON_BIG,
+ reinterpret_cast<LPARAM>(windows_icon)));
+ if (old_icon)
+ DestroyIcon(old_icon);
+ }
+}
+
+NonClientFrameView* WindowWin::CreateFrameViewForWindow() {
+ if (non_client_view_->UseNativeFrame())
+ return new NativeFrameView(this);
+ return new CustomFrameView(this);
+}
+
+void WindowWin::UpdateFrameAfterFrameChange() {
+ // We've either gained or lost a custom window region, so reset it now.
+ ResetWindowRegion(true);
+}
+
+WindowDelegate* WindowWin::GetDelegate() const {
+ return window_delegate_;
+}
+
+NonClientView* WindowWin::GetNonClientView() const {
+ return non_client_view_;
+}
+
+ClientView* WindowWin::GetClientView() const {
+ return non_client_view_->client_view();
+}
+
+gfx::NativeWindow WindowWin::GetNativeWindow() const {
+ return GetNativeView();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// WindowWin, protected:
+
+WindowWin::WindowWin(WindowDelegate* window_delegate)
+ : WidgetWin(),
+ focus_on_creation_(true),
+ window_delegate_(window_delegate),
+ non_client_view_(new NonClientView(this)),
+ owning_hwnd_(NULL),
+ minimum_size_(100, 100),
+ is_modal_(false),
+ restored_enabled_(false),
+ is_always_on_top_(false),
+ fullscreen_(false),
+ window_closed_(false),
+ disable_inactive_rendering_(false),
+ is_active_(false),
+ lock_updates_(false),
+ saved_window_style_(0),
+ saved_maximized_state_(0),
+ ignore_window_pos_changes_(false),
+ ignore_pos_changes_factory_(this),
+ force_hidden_count_(0),
+ last_monitor_(NULL) {
+ is_window_ = true;
+ InitClass();
+ DCHECK(window_delegate_);
+ window_delegate_->window_.reset(this);
+ // 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);
+}
+
+void WindowWin::Init(HWND parent, const gfx::Rect& bounds) {
+ // 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 WidgetWin functions may be called during initialization.
+ 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());
+
+ WidgetWin::Init(parent, bounds, true);
+ win_util::SetWindowUserData(GetNativeView(), this);
+
+ // Create the ClientView, add it to the NonClientView and add the
+ // NonClientView to the RootView. This will cause everything to be parented.
+ non_client_view_->set_client_view(window_delegate_->CreateClientView(this));
+ WidgetWin::SetContentsView(non_client_view_);
+
+ UpdateWindowTitle();
+
+ SetInitialBounds(bounds);
+ InitAlwaysOnTopState();
+
+ GetMonitorAndRects(bounds.ToRECT(), &last_monitor_, &last_monitor_rect_,
+ &last_work_area_);
+ ResetWindowRegion(false);
+}
+
+void WindowWin::SizeWindowToDefault() {
+ win_util::CenterAndSizeWindow(owning_window(), GetNativeView(),
+ non_client_view_->GetPreferredSize().ToSIZE(),
+ false);
+}
+
+void WindowWin::RunSystemMenu(const gfx::Point& point) {
+ // We need to reset and clean up any currently created system menu objects.
+ // We need to call this otherwise there's a small chance that we aren't going
+ // to get a system menu. We also can't take the return value of this
+ // function. We need to call it *again* to get a valid HMENU.
+ //::GetSystemMenu(GetNativeView(), TRUE);
+ UINT flags = TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD;
+ if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT)
+ flags |= TPM_RIGHTALIGN;
+ HMENU system_menu = ::GetSystemMenu(GetNativeView(), FALSE);
+ int id = ::TrackPopupMenu(system_menu, flags,
+ point.x(), point.y(), 0, GetNativeView(), NULL);
+ ExecuteSystemMenuCommand(id);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// WindowWin, WidgetWin overrides:
+
+void WindowWin::OnActivate(UINT action, BOOL minimized, HWND window) {
+ if (action == WA_INACTIVE)
+ SaveWindowPosition();
+}
+
+void WindowWin::OnActivateApp(BOOL active, DWORD thread_id) {
+ if (!active && thread_id != GetCurrentThreadId()) {
+ // Another application was activated, we should reset any state that
+ // disables inactive rendering now.
+ disable_inactive_rendering_ = false;
+ non_client_view_->DisableInactiveRendering(false);
+ // Update the native frame too, since it could be rendering the non-client
+ // area.
+ CallDefaultNCActivateHandler(FALSE);
+ }
+}
+
+LRESULT WindowWin::OnAppCommand(HWND window, short app_command, WORD device,
+ int keystate) {
+ // We treat APPCOMMAND ids as an extension of our command namespace, and just
+ // let the delegate figure out what to do...
+ if (!window_delegate_->ExecuteWindowsCommand(app_command))
+ return WidgetWin::OnAppCommand(window, app_command, device, keystate);
+ return 0;
+}
+
+void WindowWin::OnCommand(UINT notification_code, int command_id, HWND window) {
+ // If the notification code is > 1 it means it is control specific and we
+ // should ignore it.
+ if (notification_code > 1 ||
+ window_delegate_->ExecuteWindowsCommand(command_id)) {
+ WidgetWin::OnCommand(notification_code, command_id, window);
+ }
+}
+
+void WindowWin::OnDestroy() {
+ non_client_view_->WindowClosing();
+ RestoreEnabledIfNecessary();
+ WidgetWin::OnDestroy();
+}
+
+namespace {
+static BOOL CALLBACK SendDwmCompositionChanged(HWND window, LPARAM param) {
+ SendMessage(window, WM_DWMCOMPOSITIONCHANGED, 0, 0);
+ return TRUE;
+}
+} // namespace
+
+LRESULT WindowWin::OnDwmCompositionChanged(UINT msg, WPARAM w_param,
+ LPARAM l_param) {
+ // The window may try to paint in SetUseNativeFrame, and as a result it can
+ // get into a state where it is very unhappy with itself - rendering black
+ // behind the entire client area. This is because for some reason the
+ // SkPorterDuff::kClear_mode erase done in the RootView thinks the window is
+ // still opaque. So, to work around this we hide the window as soon as we can
+ // (now), saving off its placement so it can be properly restored once
+ // everything has settled down.
+ WINDOWPLACEMENT saved_window_placement;
+ saved_window_placement.length = sizeof(WINDOWPLACEMENT);
+ GetWindowPlacement(GetNativeView(), &saved_window_placement);
+ Hide();
+
+ // Important step: restore the window first, since our hiding hack doesn't
+ // work for maximized windows! We tell the frame not to allow itself to be
+ // made visible though, which removes the brief flicker.
+ ++force_hidden_count_;
+ ::ShowWindow(GetNativeView(), SW_RESTORE);
+ --force_hidden_count_;
+
+ // We respond to this in response to WM_DWMCOMPOSITIONCHANGED since that is
+ // the only thing we care about - we don't actually respond to WM_THEMECHANGED
+ // messages.
+ non_client_view_->SetUseNativeFrame(win_util::ShouldUseVistaFrame());
+
+ // Now that we've updated the frame, we'll want to restore our saved placement
+ // since the display should have settled down and we can be properly rendered.
+ SetWindowPlacement(GetNativeView(), &saved_window_placement);
+
+ // WM_DWMCOMPOSITIONCHANGED is only sent to top level windows, however we want
+ // to notify our children too, since we can have MDI child windows who need to
+ // update their appearance.
+ EnumChildWindows(GetNativeView(), &SendDwmCompositionChanged, NULL);
+ return 0;
+}
+
+void WindowWin::OnFinalMessage(HWND window) {
+ // Delete and NULL the delegate here once we're guaranteed to get no more
+ // messages.
+ window_delegate_->DeleteDelegate();
+ window_delegate_ = NULL;
+ WidgetWin::OnFinalMessage(window);
+}
+
+void WindowWin::OnGetMinMaxInfo(MINMAXINFO* minmax_info) {
+ gfx::Size min_window_size(GetNonClientView()->GetMinimumSize());
+ minmax_info->ptMinTrackSize.x = min_window_size.width();
+ minmax_info->ptMinTrackSize.y = min_window_size.height();
+ WidgetWin::OnGetMinMaxInfo(minmax_info);
+}
+
+namespace {
+static void EnableMenuItem(HMENU menu, UINT command, bool enabled) {
+ UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
+ EnableMenuItem(menu, command, flags);
+}
+} // namespace
+
+void WindowWin::OnInitMenu(HMENU menu) {
+ // We only need to manually enable the system menu if we're not using a native
+ // frame.
+ if (non_client_view_->UseNativeFrame())
+ WidgetWin::OnInitMenu(menu);
+
+ bool is_fullscreen = IsFullscreen();
+ bool is_minimized = IsMinimized();
+ bool is_maximized = IsMaximized();
+ bool is_restored = !is_fullscreen && !is_minimized && !is_maximized;
+
+ ScopedRedrawLock lock(this);
+ EnableMenuItem(menu, SC_RESTORE, is_minimized || is_maximized);
+ EnableMenuItem(menu, SC_MOVE, is_restored);
+ EnableMenuItem(menu, SC_SIZE, window_delegate_->CanResize() && is_restored);
+ EnableMenuItem(menu, SC_MAXIMIZE,
+ window_delegate_->CanMaximize() && !is_fullscreen && !is_maximized);
+ EnableMenuItem(menu, SC_MINIMIZE,
+ window_delegate_->CanMaximize() && !is_minimized);
+}
+
+void WindowWin::OnMouseLeave() {
+ // We only need to manually track WM_MOUSELEAVE messages between the client
+ // and non-client area when we're not using the native frame.
+ if (non_client_view_->UseNativeFrame()) {
+ SetMsgHandled(FALSE);
+ return;
+ }
+
+ bool process_mouse_exited = true;
+ POINT pt;
+ if (GetCursorPos(&pt)) {
+ LRESULT ht_component =
+ ::SendMessage(GetNativeView(), 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 WindowWin::OnNCActivate(BOOL active) {
+ is_active_ = !!active;
+
+ // If we're not using the native frame, we need to force a synchronous repaint
+ // otherwise we'll be left in the wrong activation state until something else
+ // causes a repaint later.
+ if (!non_client_view_->UseNativeFrame()) {
+ // We can get WM_NCACTIVATE before we're actually visible. If we're not
+ // visible, no need to paint.
+ if (IsWindowVisible(GetNativeView())) {
+ 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());
+ }
+ }
+
+ // If we're active again, we should be allowed to render as inactive, so
+ // tell the non-client view. This must be done independently of the check for
+ // disable_inactive_rendering_ since that check is valid even if the frame
+ // is not active, but this can only be done if we've become active.
+ if (IsActive())
+ non_client_view_->DisableInactiveRendering(false);
+
+ // Reset the disable inactive rendering state since activation has changed.
+ if (disable_inactive_rendering_) {
+ disable_inactive_rendering_ = false;
+ return CallDefaultNCActivateHandler(TRUE);
+ }
+ return CallDefaultNCActivateHandler(active);
+}
+
+LRESULT WindowWin::OnNCCalcSize(BOOL mode, LPARAM l_param) {
+ // We only need to adjust the client size/paint handling when we're not using
+ // the native frame.
+ if (non_client_view_->UseNativeFrame())
+ return WidgetWin::OnNCCalcSize(mode, l_param);
+
+ RECT* client_rect = mode ?
+ &reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0] :
+ reinterpret_cast<RECT*>(l_param);
+ if (IsMaximized()) {
+ // Make the maximized mode client rect fit the screen exactly, by
+ // subtracting the border Windows automatically adds for maximized mode.
+ int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
+ InflateRect(client_rect, -border_thickness, -border_thickness);
+
+ // Find all auto-hide taskbars along the screen edges and adjust in by the
+ // thickness of the auto-hide taskbar on each such edge, so the window isn't
+ // treated as a "fullscreen app", which would cause the taskbars to
+ // disappear.
+ HMONITOR monitor = MonitorFromWindow(GetNativeView(),
+ MONITOR_DEFAULTTONULL);
+ if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_LEFT, monitor))
+ client_rect->left += win_util::kAutoHideTaskbarThicknessPx;
+ if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_TOP, monitor))
+ client_rect->top += win_util::kAutoHideTaskbarThicknessPx;
+ if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_RIGHT, monitor))
+ client_rect->right -= win_util::kAutoHideTaskbarThicknessPx;
+ if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_BOTTOM, monitor))
+ client_rect->bottom -= win_util::kAutoHideTaskbarThicknessPx;
+
+ // We cannot return WVR_REDRAW when there is nonclient area, or Windows
+ // exhibits bugs where client pixels and child HWNDs are mispositioned by
+ // the width/height of the upper-left nonclient area.
+ return 0;
+ }
+
+ // If the window bounds change, we're going to relayout and repaint anyway.
+ // Returning WVR_REDRAW avoids an extra paint before that of the old client
+ // pixels in the (now wrong) location, and thus makes actions like resizing a
+ // window from the left edge look slightly less broken.
+ return mode ? WVR_REDRAW : 0;
+}
+
+LRESULT WindowWin::OnNCHitTest(const CPoint& point) {
+ // First, give the NonClientView a chance to test the point to see if it
+ // provides any of the non-client area.
+ CPoint temp = point;
+ MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1);
+ int component = non_client_view_->NonClientHitTest(gfx::Point(temp));
+ if (component != HTNOWHERE)
+ return component;
+
+ // Otherwise, we let Windows do all the native frame non-client handling for
+ // us.
+ return WidgetWin::OnNCHitTest(point);
+}
+
+namespace {
+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;
+}
+} // namespace
+
+void WindowWin::OnNCPaint(HRGN rgn) {
+ // We only do non-client painting if we're not using the native frame.
+ if (non_client_view_->UseNativeFrame()) {
+ WidgetWin::OnNCPaint(rgn);
+ return;
+ }
+
+ // 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);
+
+ if (window_rect.Width() != root_view_->width() ||
+ window_rect.Height() != root_view_->height()) {
+ // If the size of the window differs from the size of the root view it
+ // means we're being asked to paint before we've gotten a WM_SIZE. This can
+ // happen when the user is interactively resizing the window. To avoid
+ // mass flickering we don't do anything here. Once we get the WM_SIZE we'll
+ // reset the region of the window which triggers another WM_NCPAINT and
+ // all is well.
+ return;
+ }
+
+ 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(GetNativeView());
+ ClipState clip_state;
+ clip_state.x = window_rect.left;
+ clip_state.y = window_rect.top;
+ clip_state.parent = GetNativeView();
+ clip_state.dc = dc;
+ EnumChildWindows(GetNativeView(), &ClipDCToChild,
+ reinterpret_cast<LPARAM>(&clip_state));
+
+ RootView* root_view = GetRootView();
+ gfx::Rect old_paint_region =
+ root_view->GetScheduledPaintRectConstrainedToSize();
+
+ if (!old_paint_region.IsEmpty()) {
+ // The root view has a region that needs to be painted. Include it in the
+ // region we're going to paint.
+
+ CRect old_paint_region_crect = old_paint_region.ToRECT();
+ CRect tmp = dirty_region;
+ UnionRect(&dirty_region, &tmp, &old_paint_region_crect);
+ }
+
+ root_view->SchedulePaint(gfx::Rect(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(GetNativeView(), dc);
+}
+
+void WindowWin::OnNCLButtonDown(UINT ht_component, const CPoint& point) {
+ // When we're using a native frame, window controls work without us
+ // interfering.
+ if (!non_client_view_->UseNativeFrame()) {
+ 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
+ // 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.
+ ProcessNCMousePress(point, MK_LBUTTON);
+ return;
+ }
+ }
+ }
+
+ // TODO(beng): figure out why we need to run the system menu manually
+ // ourselves. This is wrong and causes many subtle bugs.
+ // From my initial research, it looks like DefWindowProc tries
+ // to run it but fails before sending the initial WM_MENUSELECT
+ // for the sysmenu.
+ if (ht_component == HTSYSMENU)
+ RunSystemMenu(non_client_view_->GetSystemMenuPoint());
+ else
+ WidgetWin::OnNCLButtonDown(ht_component, point);
+
+ /* TODO(beng): Fix the standard non-client over-painting bug. This code
+ doesn't work but identifies the problem.
+ if (!IsMsgHandled()) {
+ // WindowWin::OnNCLButtonDown set the message as unhandled. This normally
+ // means WidgetWin::ProcessWindowMessage will pass it to
+ // DefWindowProc. Sadly, DefWindowProc for WM_NCLBUTTONDOWN does weird
+ // non-client painting, so we need to call it directly here inside a
+ // scoped update lock.
+ ScopedRedrawLock lock(this);
+ DefWindowProc(GetNativeView(), WM_NCLBUTTONDOWN, ht_component,
+ MAKELPARAM(point.x, point.y));
+ SetMsgHandled(TRUE);
+ }
+ */
+}
+
+void WindowWin::OnNCRButtonDown(UINT ht_component, const CPoint& point) {
+ if (ht_component == HTCAPTION || ht_component == HTSYSMENU)
+ RunSystemMenu(gfx::Point(point));
+ else
+ WidgetWin::OnNCRButtonDown(ht_component, point);
+}
+
+LRESULT WindowWin::OnNCUAHDrawCaption(UINT msg, WPARAM w_param,
+ LPARAM l_param) {
+ // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for
+ // an explanation about why we need to handle this message.
+ SetMsgHandled(!non_client_view_->UseNativeFrame());
+ return 0;
+}
+
+LRESULT WindowWin::OnNCUAHDrawFrame(UINT msg, WPARAM w_param,
+ LPARAM l_param) {
+ // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for
+ // an explanation about why we need to handle this message.
+ SetMsgHandled(!non_client_view_->UseNativeFrame());
+ return 0;
+}
+
+LRESULT WindowWin::OnSetCursor(HWND window, UINT hittest_code, UINT message) {
+ // If the window is disabled, it's because we're showing a modal dialog box.
+ // We need to let DefWindowProc handle the message. That's because
+ // DefWindowProc for WM_SETCURSOR with message = some kind of mouse button
+ // down message sends the top level window a WM_ACTIVATEAPP message, which we
+ // otherwise wouldn't get. The symptom of not doing this is that if the user
+ // has a window in the background with a modal dialog open, they can't click
+ // on the disabled background window to bring the entire stack to the front.
+ // This is annoying because they then have to move all the foreground windows
+ // out of the way to be able to activate said window. I love how on Windows,
+ // the answer isn't always logical.
+ if (!IsWindowEnabled(GetNativeView()))
+ return WidgetWin::OnSetCursor(window, hittest_code, 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;
+}
+
+LRESULT WindowWin::OnSetIcon(UINT size_type, HICON new_icon) {
+ // This shouldn't hurt even if we're using the native frame.
+ ScopedRedrawLock lock(this);
+ return DefWindowProc(GetNativeView(), WM_SETICON, size_type,
+ reinterpret_cast<LPARAM>(new_icon));
+}
+
+LRESULT WindowWin::OnSetText(const wchar_t* text) {
+ // This shouldn't hurt even if we're using the native frame.
+ ScopedRedrawLock lock(this);
+ return DefWindowProc(GetNativeView(), WM_SETTEXT, NULL,
+ reinterpret_cast<LPARAM>(text));
+}
+
+void WindowWin::OnSettingChange(UINT flags, const wchar_t* section) {
+ if (!GetParent() && (flags == SPI_SETWORKAREA)) {
+ // Fire a dummy SetWindowPos() call, so we'll trip the code in
+ // OnWindowPosChanging() below that notices work area changes.
+ ::SetWindowPos(GetNativeView(), 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
+ SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+ SetMsgHandled(TRUE);
+ } else {
+ WidgetWin::OnSettingChange(flags, section);
+ }
+}
+
+void WindowWin::OnSize(UINT size_param, const CSize& new_size) {
+ // Don't no-op if the new_size matches current size. If our normal bounds
+ // and maximized bounds are the same, then we need to layout (because we
+ // layout differently when maximized).
+ SaveWindowPosition();
+ ChangeSize(size_param, new_size);
+ RedrawWindow(GetNativeView(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN);
+
+ // ResetWindowRegion is going to trigger WM_NCPAINT. By doing it after we've
+ // invoked OnSize we ensure the RootView has been laid out.
+ ResetWindowRegion(false);
+}
+
+void WindowWin::OnSysCommand(UINT notification_code, CPoint click) {
+ // Windows uses the 4 lower order bits of |notification_code| for type-
+ // specific information so we must exclude this when comparing.
+ static const int sc_mask = 0xFFF0;
+ // Ignore size/move/maximize in fullscreen mode.
+ if (IsFullscreen() &&
+ (((notification_code & sc_mask) == SC_SIZE) ||
+ ((notification_code & sc_mask) == SC_MOVE) ||
+ ((notification_code & sc_mask) == SC_MAXIMIZE)))
+ return;
+ if (!non_client_view_->UseNativeFrame()) {
+ if ((notification_code & sc_mask) == SC_MINIMIZE ||
+ (notification_code & sc_mask) == SC_MAXIMIZE ||
+ (notification_code & sc_mask) == SC_RESTORE) {
+ non_client_view_->ResetWindowControls();
+ } else if ((notification_code & sc_mask) == SC_MOVE ||
+ (notification_code & sc_mask) == SC_SIZE) {
+ if (lock_updates_) {
+ // We were locked, before entering a resize or move modal loop. Now that
+ // we've begun to move the window, we need to unlock updates so that the
+ // sizing/moving feedback can be continuous.
+ UnlockUpdates();
+ }
+ }
+ }
+
+ // First see if the delegate can handle it.
+ if (window_delegate_->ExecuteWindowsCommand(notification_code))
+ return;
+
+ if (notification_code == IDC_ALWAYS_ON_TOP) {
+ is_always_on_top_ = !is_always_on_top_;
+
+ // Change the menu check state.
+ HMENU system_menu = GetSystemMenu(GetNativeView(), 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 if ((notification_code == SC_KEYMENU) && (click.x == VK_SPACE)) {
+ // Run the system menu at the NonClientView's desired location.
+ RunSystemMenu(non_client_view_->GetSystemMenuPoint());
+ } else {
+ // Use the default implementation for any other command.
+ DefWindowProc(GetNativeView(), WM_SYSCOMMAND, notification_code,
+ MAKELPARAM(click.y, click.x));
+ }
+}
+
+void WindowWin::OnWindowPosChanging(WINDOWPOS* window_pos) {
+ if (force_hidden_count_) {
+ // Prevent the window from being made visible if we've been asked to do so.
+ // See comment in header as to why we might want this.
+ window_pos->flags &= ~SWP_SHOWWINDOW;
+ }
+
+ if (ignore_window_pos_changes_) {
+ // If somebody's trying to toggle our visibility, change the nonclient area,
+ // change our Z-order, or activate us, we should probably let it go through.
+ if (!(window_pos->flags & ((IsVisible() ? SWP_HIDEWINDOW : SWP_SHOWWINDOW) |
+ SWP_FRAMECHANGED)) &&
+ (window_pos->flags & (SWP_NOZORDER | SWP_NOACTIVATE))) {
+ // Just sizing/moving the window; ignore.
+ window_pos->flags |= SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW;
+ window_pos->flags &= ~(SWP_SHOWWINDOW | SWP_HIDEWINDOW);
+ }
+ } else if (!GetParent()) {
+ CRect window_rect;
+ HMONITOR monitor;
+ gfx::Rect monitor_rect, work_area;
+ if (GetWindowRect(&window_rect) &&
+ GetMonitorAndRects(window_rect, &monitor, &monitor_rect, &work_area)) {
+ if (monitor && (monitor == last_monitor_) &&
+ (IsFullscreen() || ((monitor_rect == last_monitor_rect_) &&
+ (work_area != last_work_area_)))) {
+ // A rect for the monitor we're on changed. Normally Windows notifies
+ // us about this (and thus we're reaching here due to the SetWindowPos()
+ // call in OnSettingChange() above), but with some software (e.g.
+ // nVidia's nView desktop manager) the work area can change asynchronous
+ // to any notification, and we're just sent a SetWindowPos() call with a
+ // new (frequently incorrect) position/size. In either case, the best
+ // response is to throw away the existing position/size information in
+ // |window_pos| and recalculate it based on the new work rect.
+ gfx::Rect new_window_rect;
+ if (IsFullscreen()) {
+ new_window_rect = monitor_rect;
+ } else if (IsZoomed()) {
+ new_window_rect = work_area;
+ int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
+ new_window_rect.Inset(-border_thickness, -border_thickness);
+ } else {
+ new_window_rect = gfx::Rect(window_rect).AdjustToFit(work_area);
+ }
+ window_pos->x = new_window_rect.x();
+ window_pos->y = new_window_rect.y();
+ window_pos->cx = new_window_rect.width();
+ window_pos->cy = new_window_rect.height();
+ // WARNING! Don't set SWP_FRAMECHANGED here, it breaks moving the child
+ // HWNDs for some reason.
+ window_pos->flags &= ~(SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW);
+ window_pos->flags |= SWP_NOCOPYBITS;
+
+ // Now ignore all immediately-following SetWindowPos() changes. Windows
+ // likes to (incorrectly) recalculate what our position/size should be
+ // and send us further updates.
+ ignore_window_pos_changes_ = true;
+ DCHECK(ignore_pos_changes_factory_.empty());
+ MessageLoop::current()->PostTask(FROM_HERE,
+ ignore_pos_changes_factory_.NewRunnableMethod(
+ &WindowWin::StopIgnoringPosChanges));
+ }
+ last_monitor_ = monitor;
+ last_monitor_rect_ = monitor_rect;
+ last_work_area_ = work_area;
+ }
+ }
+
+ WidgetWin::OnWindowPosChanging(window_pos);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowWin, private:
+
+void WindowWin::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 WindowWin::SetInitialFocus() {
+ if (!focus_on_creation_)
+ return;
+
+ View* v = window_delegate_->GetInitiallyFocusedView();
+ if (v) {
+ v->RequestFocus();
+ } else {
+ // The window does not get keyboard messages unless we focus it, not sure
+ // why.
+ SetFocus(GetNativeView());
+ }
+}
+
+void WindowWin::SetInitialBounds(const gfx::Rect& create_bounds) {
+ // First we obtain the window's saved show-style and store it. We need to do
+ // this here, rather than in Show() because by the time Show() is called,
+ // the window's size will have been reset (below) and the saved maximized
+ // state will have been lost. Sadly there's no way to tell on Windows when
+ // a window is restored from maximized state, so we can't more accurately
+ // track maximized state independently of sizing information.
+ window_delegate_->GetSavedMaximizedState(&saved_maximized_state_);
+
+ // Restore the window's placement from the controller.
+ gfx::Rect saved_bounds(create_bounds.ToRECT());
+ if (window_delegate_->GetSavedWindowBounds(&saved_bounds)) {
+ // Make sure the bounds are at least the minimum size.
+ if (saved_bounds.width() < minimum_size_.cx) {
+ saved_bounds.SetRect(saved_bounds.x(), saved_bounds.y(),
+ saved_bounds.right() + minimum_size_.cx -
+ saved_bounds.width(),
+ saved_bounds.bottom());
+ }
+
+ if (saved_bounds.height() < minimum_size_.cy) {
+ saved_bounds.SetRect(saved_bounds.x(), saved_bounds.y(),
+ saved_bounds.right(),
+ saved_bounds.bottom() + minimum_size_.cy -
+ saved_bounds.height());
+ }
+
+ // "Show state" (maximized, minimized, etc) is handled by Show().
+ // Don't use SetBounds here. SetBounds constrains to the size of the
+ // monitor, but we don't want that when creating a new window as the result
+ // of dragging out a tab to create a new window.
+ SetWindowPos(NULL, saved_bounds.x(), saved_bounds.y(),
+ saved_bounds.width(), saved_bounds.height(), 0);
+ } else {
+ if (create_bounds.IsEmpty()) {
+ // No initial bounds supplied, so size the window to its content and
+ // center over its parent.
+ SizeWindowToDefault();
+ } else {
+ // Use the supplied initial bounds.
+ SetBounds(create_bounds);
+ }
+ }
+}
+
+void WindowWin::InitAlwaysOnTopState() {
+ is_always_on_top_ = false;
+ if (window_delegate_->GetSavedAlwaysOnTopState(&is_always_on_top_) &&
+ is_always_on_top_ != window_delegate_->IsAlwaysOnTop()) {
+ AlwaysOnTopChanged();
+ }
+
+ if (window_delegate_->HasAlwaysOnTopMenu())
+ AddAlwaysOnTopSystemMenuItem();
+}
+
+void WindowWin::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(GetNativeView(), 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 WindowWin::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 WindowWin::AlwaysOnTopChanged() {
+ ::SetWindowPos(GetNativeView(),
+ is_always_on_top_ ? HWND_TOPMOST : HWND_NOTOPMOST,
+ 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+}
+
+DWORD WindowWin::CalculateWindowStyle() {
+ DWORD window_styles =
+ WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_SYSMENU | WS_CAPTION;
+ bool can_resize = window_delegate_->CanResize();
+ bool can_maximize = window_delegate_->CanMaximize();
+ if (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;
+}
+
+DWORD WindowWin::CalculateWindowExStyle() {
+ DWORD window_ex_styles = 0;
+ if (window_delegate_->AsDialogDelegate())
+ window_ex_styles |= WS_EX_DLGMODALFRAME;
+ if (window_delegate_->IsAlwaysOnTop())
+ window_ex_styles |= WS_EX_TOPMOST;
+ return window_ex_styles;
+}
+
+void WindowWin::SaveWindowPosition() {
+ // The window delegate does the actual saving for us. It seems like (judging
+ // by go/crash) that in some circumstances we can end up here after
+ // WM_DESTROY, at which point the window delegate is likely gone. So just
+ // bail.
+ if (!window_delegate_)
+ return;
+
+ WINDOWPLACEMENT win_placement = { 0 };
+ win_placement.length = sizeof(WINDOWPLACEMENT);
+
+ BOOL r = GetWindowPlacement(GetNativeView(), &win_placement);
+ DCHECK(r);
+
+ bool maximized = (win_placement.showCmd == SW_SHOWMAXIMIZED);
+ CRect window_bounds(win_placement.rcNormalPosition);
+ window_delegate_->SaveWindowPlacement(
+ gfx::Rect(win_placement.rcNormalPosition), maximized, is_always_on_top_);
+}
+
+void WindowWin::LockUpdates() {
+ lock_updates_ = true;
+ saved_window_style_ = GetWindowLong(GWL_STYLE);
+ SetWindowLong(GWL_STYLE, saved_window_style_ & ~WS_VISIBLE);
+}
+
+void WindowWin::UnlockUpdates() {
+ SetWindowLong(GWL_STYLE, saved_window_style_);
+ lock_updates_ = false;
+}
+
+void WindowWin::ResetWindowRegion(bool force) {
+ // A native frame uses the native window region, and we don't want to mess
+ // with it.
+ if (non_client_view_->UseNativeFrame()) {
+ if (force)
+ SetWindowRgn(NULL, TRUE);
+ return;
+ }
+
+ // 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(GetNativeView(), current_rgn);
+
+ CRect window_rect;
+ GetWindowRect(&window_rect);
+ HRGN new_region;
+ 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 {
+ DeleteObject(new_region);
+ }
+
+ DeleteObject(current_rgn);
+}
+
+void WindowWin::ProcessNCMousePress(const CPoint& point, int flags) {
+ CPoint temp = point;
+ MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1);
+ UINT message_flags = 0;
+ if ((GetKeyState(VK_CONTROL) & 0x80) == 0x80)
+ message_flags |= MK_CONTROL;
+ if ((GetKeyState(VK_SHIFT) & 0x80) == 0x80)
+ message_flags |= MK_SHIFT;
+ message_flags |= flags;
+ ProcessMousePressed(temp, message_flags, false, false);
+}
+
+LRESULT WindowWin::CallDefaultNCActivateHandler(BOOL active) {
+ // The DefWindowProc handling for WM_NCACTIVATE renders the classic-look
+ // window title bar directly, so we need to use a redraw lock here to prevent
+ // it from doing so.
+ ScopedRedrawLock lock(this);
+ return DefWindowProc(GetNativeView(), WM_NCACTIVATE, active, 0);
+}
+
+void WindowWin::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;
+ }
+}
+
+namespace {
+// static
+static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) {
+ WidgetWin* widget = reinterpret_cast<WidgetWin*>(
+ win_util::GetWindowUserData(hwnd));
+ if (!widget)
+ return TRUE;
+
+ // If the toplevel HWND is a Window, close it if it's identified as a
+ // secondary window.
+ Window* window = widget->GetWindow();
+ if (window) {
+ if (!window->IsAppWindow())
+ window->Close();
+ } else {
+ // If it's not a Window, then close it anyway since it probably is
+ // secondary.
+ widget->Close();
+ }
+ return TRUE;
+}
+} // namespace
+
+void Window::CloseAllSecondaryWindows() {
+ EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, 0);
+}
+
+} // namespace views
diff --git a/views/window/window_win.h b/views/window/window_win.h
new file mode 100644
index 0000000..76d5196
--- /dev/null
+++ b/views/window/window_win.h
@@ -0,0 +1,307 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_WINDOW_WINDOW_WIN_H_
+#define VIEWS_WINDOW_WINDOW_WIN_H_
+
+#include "views/widget/widget_win.h"
+#include "views/window/client_view.h"
+#include "views/window/non_client_view.h"
+#include "views/window/window.h"
+
+namespace gfx {
+class Point;
+class Size;
+};
+
+namespace views {
+
+class Client;
+class WindowDelegate;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// WindowWin
+//
+// A WindowWin is a WidgetWin that has a caption and a border. The frame is
+// rendered by the operating system.
+//
+///////////////////////////////////////////////////////////////////////////////
+class WindowWin : public WidgetWin,
+ public Window {
+ public:
+ virtual ~WindowWin();
+
+ // Show the window with the specified show command.
+ void Show(int show_state);
+
+ // Retrieve the show state of the window. This is one of the SW_SHOW* flags
+ // passed into Windows' ShowWindow method. For normal windows this defaults
+ // to SW_SHOWNORMAL, however windows (e.g. the main window) can override this
+ // method to provide different values (e.g. retrieve the user's specified
+ // show state from the shortcut starutp info).
+ virtual int GetShowState() const;
+
+ // Executes the specified SC_command.
+ void ExecuteSystemMenuCommand(int command);
+
+ // Hides the window if it hasn't already been force-hidden, then increments
+ // |force_hidden_count_| to prevent it from being shown again until
+ // PopForceHidden()) is called.
+ void PushForceHidden();
+
+ // Decrements |force_hidden_count_| and, if it is now zero, shows the window.
+ void PopForceHidden();
+
+ // Accessors and setters for various properties.
+ HWND owning_window() const { return owning_hwnd_; }
+ void set_focus_on_creation(bool focus_on_creation) {
+ focus_on_creation_ = focus_on_creation;
+ }
+
+ // Window overrides:
+ virtual gfx::Rect GetBounds() const;
+ virtual gfx::Rect GetNormalBounds() const;
+ virtual void SetBounds(const gfx::Rect& bounds);
+ virtual void SetBounds(const gfx::Rect& bounds,
+ gfx::NativeWindow other_window);
+ virtual void Show();
+ virtual void Activate();
+ virtual void Close();
+ virtual void Maximize();
+ virtual void Minimize();
+ virtual void Restore();
+ virtual bool IsActive() const;
+ virtual bool IsVisible() const;
+ virtual bool IsMaximized() const;
+ virtual bool IsMinimized() const;
+ virtual void SetFullscreen(bool fullscreen);
+ virtual bool IsFullscreen() const;
+ virtual void EnableClose(bool enable);
+ virtual void DisableInactiveRendering();
+ virtual void UpdateWindowTitle();
+ virtual void UpdateWindowIcon();
+ virtual NonClientFrameView* CreateFrameViewForWindow();
+ virtual void UpdateFrameAfterFrameChange();
+ virtual WindowDelegate* GetDelegate() const;
+ virtual NonClientView* GetNonClientView() const;
+ virtual ClientView* GetClientView() const;
+ virtual gfx::NativeWindow GetNativeWindow() const;
+
+ protected:
+ friend Window;
+
+ // Constructs the WindowWin. |window_delegate| cannot be NULL.
+ explicit WindowWin(WindowDelegate* window_delegate);
+
+ // Create the Window.
+ // If parent is NULL, this WindowWin is top level on the desktop.
+ // If |bounds| is empty, the view is queried for its preferred size and
+ // centered on screen.
+ virtual void Init(HWND parent, const gfx::Rect& bounds);
+
+ // Sizes the window to the default size specified by its ClientView.
+ virtual void SizeWindowToDefault();
+
+ // Shows the system menu at the specified screen point.
+ void RunSystemMenu(const gfx::Point& point);
+
+ // Overridden from WidgetWin:
+ virtual void OnActivate(UINT action, BOOL minimized, HWND window);
+ virtual void OnActivateApp(BOOL active, DWORD thread_id);
+ virtual LRESULT OnAppCommand(HWND window, short app_command, WORD device,
+ int keystate);
+ virtual void OnCommand(UINT notification_code, int command_id, HWND window);
+ virtual void OnDestroy();
+ virtual LRESULT OnDwmCompositionChanged(UINT msg, WPARAM w_param,
+ LPARAM l_param);
+ virtual void OnFinalMessage(HWND window);
+ virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info);
+ 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 void OnNCPaint(HRGN rgn);
+ virtual void OnNCLButtonDown(UINT ht_component, const CPoint& point);
+ virtual void OnNCRButtonDown(UINT ht_component, const CPoint& point);
+ virtual LRESULT OnNCUAHDrawCaption(UINT msg, WPARAM w_param, LPARAM l_param);
+ virtual LRESULT OnNCUAHDrawFrame(UINT msg, WPARAM w_param, LPARAM l_param);
+ virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message);
+ virtual LRESULT OnSetIcon(UINT size_type, HICON new_icon);
+ virtual LRESULT OnSetText(const wchar_t* text);
+ virtual void OnSettingChange(UINT flags, const wchar_t* section);
+ virtual void OnSize(UINT size_param, const CSize& new_size);
+ virtual void OnSysCommand(UINT notification_code, CPoint click);
+ virtual void OnWindowPosChanging(WINDOWPOS* window_pos);
+ virtual Window* GetWindow() { return this; }
+ virtual const Window* GetWindow() const { return this; }
+
+ // Accessor for disable_inactive_rendering_.
+ bool disable_inactive_rendering() const {
+ return disable_inactive_rendering_;
+ }
+
+ private:
+ // Information saved before going into fullscreen mode, used to restore the
+ // window afterwards.
+ struct SavedWindowInfo {
+ bool maximized;
+ LONG style;
+ LONG ex_style;
+ RECT window_rect;
+ };
+
+ // Set the window as modal (by disabling all the other windows).
+ void BecomeModal();
+
+ // 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();
+
+ // Place and size the window when it is created. |create_bounds| are the
+ // bounds used when the window was created.
+ void SetInitialBounds(const gfx::Rect& create_bounds);
+
+ // Restore saved always on stop state and add the always on top system menu
+ // if needed.
+ void InitAlwaysOnTopState();
+
+ // 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();
+
+ // Lock or unlock the window from being able to redraw itself in response to
+ // updates to its invalid region.
+ class ScopedRedrawLock;
+ void LockUpdates();
+ void UnlockUpdates();
+
+ // Stops ignoring SetWindowPos() requests (see below).
+ void StopIgnoringPosChanges() { ignore_window_pos_changes_ = false; }
+
+ // Resets the window region for the current window bounds if necessary.
+ // If |force| is true, the window region is reset to NULL even for native
+ // frame windows.
+ void ResetWindowRegion(bool force);
+
+ // Converts a non-client mouse down message to a regular ChromeViews event
+ // and handle it. |point| is the mouse position of the message in screen
+ // coords. |flags| are flags that would be passed with a WM_L/M/RBUTTON*
+ // message and relate to things like which button was pressed. These are
+ // combined with flags relating to the current key state.
+ void ProcessNCMousePress(const CPoint& point, int flags);
+
+ // Calls the default WM_NCACTIVATE handler with the specified activation
+ // value, safely wrapping the call in a ScopedRedrawLock to prevent frame
+ // flicker.
+ LRESULT CallDefaultNCActivateHandler(BOOL 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];
+
+ // Our window delegate (see Init method for documentation).
+ WindowDelegate* window_delegate_;
+
+ // The View that provides the non-client area of the window (title bar,
+ // window controls, sizing borders etc). To use an implementation other than
+ // the default, this class must be subclassed and this value set to the
+ // desired implementation before calling |Init|.
+ NonClientView* non_client_view_;
+
+ // 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_;
+
+ // True if we're in fullscreen mode.
+ bool fullscreen_;
+
+ // Saved window information from before entering fullscreen mode.
+ SavedWindowInfo saved_window_info_;
+
+ // Set to true if the window is in the process of closing .
+ bool window_closed_;
+
+ // True when the window should be rendered as active, regardless of whether
+ // or not it actually is.
+ bool disable_inactive_rendering_;
+
+ // True if this window is the active top level window.
+ bool is_active_;
+
+ // True if updates to this window are currently locked.
+ bool lock_updates_;
+
+ // The window styles of the window before updates were locked.
+ DWORD saved_window_style_;
+
+ // The saved maximized state for this window. See note in SetInitialBounds
+ // that explains why we save this.
+ bool saved_maximized_state_;
+
+ // When true, this flag makes us discard incoming SetWindowPos() requests that
+ // only change our position/size. (We still allow changes to Z-order,
+ // activation, etc.)
+ bool ignore_window_pos_changes_;
+
+ // The following factory is used to ignore SetWindowPos() calls for short time
+ // periods.
+ ScopedRunnableMethodFactory<WindowWin> ignore_pos_changes_factory_;
+
+ // If this is greater than zero, we should prevent attempts to make the window
+ // visible when we handle WM_WINDOWPOSCHANGING. Some calls like
+ // ShowWindow(SW_RESTORE) make the window visible in addition to restoring it,
+ // when all we want to do is restore it.
+ int force_hidden_count_;
+
+ // The last-seen monitor containing us, and its rect and work area. These are
+ // used to catch updates to the rect and work area and react accordingly.
+ HMONITOR last_monitor_;
+ gfx::Rect last_monitor_rect_, last_work_area_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowWin);
+};
+
+} // namespace views
+
+#endif // VIEWS_WINDOW_WINDOW_WIN_H_