summaryrefslogtreecommitdiffstats
path: root/views/controls
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
commit2362e4fe2905ab75d3230ebc3e307ae53e2b8362 (patch)
treee6d88357a2021811e0e354f618247217be8bb3da /views/controls
parentdb23ac3e713dc17509b2b15d3ee634968da45715 (diff)
downloadchromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.zip
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.gz
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.bz2
Move src/chrome/views to src/views. RS=darin http://crbug.com/11387
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15604 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/controls')
-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
75 files changed, 20073 insertions, 0 deletions
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_