summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-07 03:25:48 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-07 03:25:48 +0000
commitdbb66e6bf4914172a96f5121b1ffcad73fb0826d (patch)
treed8ad643a76f008edd687b2a6fc73fbc0293f2227
parentb5630e5b4504b033d8262eb902ddc867477d1cdf (diff)
downloadchromium_src-dbb66e6bf4914172a96f5121b1ffcad73fb0826d.zip
chromium_src-dbb66e6bf4914172a96f5121b1ffcad73fb0826d.tar.gz
chromium_src-dbb66e6bf4914172a96f5121b1ffcad73fb0826d.tar.bz2
Views based implementation of treeview. There are a couple of things
left to resolve: editing, context menus and auto_expand_children. BUG=109141 TEST=none R=ben@chromium.org Review URL: http://codereview.chromium.org/9120014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@116807 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc2
-rw-r--r--chrome/browser/ui/views/collected_cookies_win.cc9
-rw-r--r--ui/views/controls/scroll_view.cc21
-rw-r--r--ui/views/controls/tree/tree_view.h6
-rw-r--r--ui/views/controls/tree/tree_view_controller.cc3
-rw-r--r--ui/views/controls/tree/tree_view_controller.h3
-rw-r--r--ui/views/controls/tree/tree_view_views.cc779
-rw-r--r--ui/views/controls/tree/tree_view_views.h384
-rw-r--r--ui/views/controls/tree/tree_view_views_unittest.cc384
-rw-r--r--ui/views/controls/tree/tree_view_win.cc13
-rw-r--r--ui/views/controls/tree/tree_view_win.h4
-rw-r--r--ui/views/examples/examples_window.cc4
-rw-r--r--ui/views/examples/tree_view_example.cc109
-rw-r--r--ui/views/examples/tree_view_example.h58
-rw-r--r--ui/views/views.gyp18
15 files changed, 1580 insertions, 217 deletions
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index aaa62a4..7c9d08e 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -400,7 +400,7 @@ void BookmarkEditorView::Init() {
if (show_tree_) {
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(1, single_column_view_set_id);
- layout->AddView(tree_view_);
+ layout->AddView(tree_view_->CreateParentIfNecessary());
}
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
diff --git a/chrome/browser/ui/views/collected_cookies_win.cc b/chrome/browser/ui/views/collected_cookies_win.cc
index dae83c2..bf960ef 100644
--- a/chrome/browser/ui/views/collected_cookies_win.cc
+++ b/chrome/browser/ui/views/collected_cookies_win.cc
@@ -276,8 +276,9 @@ views::View* CollectedCookiesWin::CreateAllowedPane() {
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(1, single_column_layout_id);
- layout->AddView(allowed_cookies_tree_, 1, 1, GridLayout::FILL,
- GridLayout::FILL, kTreeViewWidth, kTreeViewHeight);
+ layout->AddView(allowed_cookies_tree_->CreateParentIfNecessary(), 1, 1,
+ GridLayout::FILL, GridLayout::FILL, kTreeViewWidth,
+ kTreeViewHeight);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(0, single_column_layout_id);
@@ -342,8 +343,8 @@ views::View* CollectedCookiesWin::CreateBlockedPane() {
layout->StartRow(1, single_column_layout_id);
layout->AddView(
- blocked_cookies_tree_, 1, 1, GridLayout::FILL, GridLayout::FILL,
- kTreeViewWidth, kTreeViewHeight);
+ blocked_cookies_tree_->CreateParentIfNecessary(), 1, 1,
+ GridLayout::FILL, GridLayout::FILL, kTreeViewWidth, kTreeViewHeight);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(0, three_columns_layout_id);
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index 4c1017e..ad6b88b 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -18,11 +18,11 @@ class Viewport : public View {
Viewport() {}
virtual ~Viewport() {}
- virtual std::string GetClassName() const {
+ virtual std::string GetClassName() const OVERRIDE {
return "views/Viewport";
}
- virtual void ScrollRectToVisible(const gfx::Rect& rect) {
+ virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
if (!has_children() || !parent())
return;
@@ -33,6 +33,11 @@ class Viewport : public View {
scroll_rect);
}
+ void ChildPreferredSizeChanged(View* child) OVERRIDE {
+ if (parent())
+ parent()->Layout();
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(Viewport);
};
@@ -141,13 +146,13 @@ void ScrollView::ComputeScrollBarsVisibility(const gfx::Size& vp_size,
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
+ // use 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.
+ // 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();
// Realign it to 0 so it can be used as-is for SetBounds().
viewport_bounds.set_origin(gfx::Point(0, 0));
diff --git a/ui/views/controls/tree/tree_view.h b/ui/views/controls/tree/tree_view.h
index 4072fd8..c266c93 100644
--- a/ui/views/controls/tree/tree_view.h
+++ b/ui/views/controls/tree/tree_view.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -6,8 +6,10 @@
#define UI_VIEWS_CONTROLS_TREE_TREE_VIEW_H_
#pragma once
-#if defined(OS_WIN)
+#if defined(OS_WIN) && !defined(USE_AURA)
#include "ui/views/controls/tree/tree_view_win.h"
+#else
+#include "ui/views/controls/tree/tree_view_views.h"
#endif
#endif // UI_VIEWS_CONTROLS_TREE_TREE_VIEW_H_
diff --git a/ui/views/controls/tree/tree_view_controller.cc b/ui/views/controls/tree/tree_view_controller.cc
index 516522a..670d0ed 100644
--- a/ui/views/controls/tree/tree_view_controller.cc
+++ b/ui/views/controls/tree/tree_view_controller.cc
@@ -13,9 +13,6 @@ bool TreeViewController::CanEdit(TreeView* tree_view, ui::TreeModelNode* node) {
return true;
}
-void TreeViewController::OnTreeViewKeyDown(ui::KeyboardCode keycode) {
-}
-
TreeViewController::~TreeViewController() {
}
diff --git a/ui/views/controls/tree/tree_view_controller.h b/ui/views/controls/tree/tree_view_controller.h
index 2100e21..2fe24f8 100644
--- a/ui/views/controls/tree/tree_view_controller.h
+++ b/ui/views/controls/tree/tree_view_controller.h
@@ -30,9 +30,6 @@ class VIEWS_EXPORT TreeViewController {
// TreeView is editable.
virtual bool CanEdit(TreeView* tree_view, ui::TreeModelNode* node);
- // Invoked when a key is pressed on the tree view.
- virtual void OnTreeViewKeyDown(ui::KeyboardCode keycode);
-
protected:
virtual ~TreeViewController();
};
diff --git a/ui/views/controls/tree/tree_view_views.cc b/ui/views/controls/tree/tree_view_views.cc
new file mode 100644
index 0000000..666e35f
--- /dev/null
+++ b/ui/views/controls/tree/tree_view_views.cc
@@ -0,0 +1,779 @@
+// Copyright (c) 2012 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 "ui/views/controls/tree/tree_view_views.h"
+
+#include <algorithm>
+
+#include "base/i18n/rtl.h"
+#include "grit/ui_resources.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/tree/tree_view_controller.h"
+
+using ui::TreeModel;
+using ui::TreeModelNode;
+
+namespace views {
+
+// Insets around the view.
+static const int kHorizontalInset = 2;
+static const int kVerticalInset = 2;
+// Padding before/after the image.
+static const int kImagePadding = 4;
+// Size of the arrow region.
+static const int kArrowRegionSize = 12;
+// Padding around the text (on each side).
+static const int kTextVerticalPadding = 3;
+static const int kTextHorizontalPadding = 2;
+// How much children are indented from their parent.
+static const int kIndent = 20;
+
+// TODO: these should come from native theme or something.
+static const SkColor kArrowColor = SkColorSetRGB(0x7A, 0x7A, 0x7A);
+static const SkColor kSelectedBackgroundColor = SkColorSetRGB(0xEE, 0xEE, 0xEE);
+static const SkColor kTextColor = SK_ColorBLACK;
+
+TreeView::TreeView()
+ : model_(NULL),
+ selected_node_(NULL),
+ editing_node_(NULL),
+ editor_(NULL),
+ auto_expand_children_(false),
+ editable_(true),
+ controller_(NULL),
+ root_shown_(true),
+ has_custom_icons_(false),
+ row_height_(font_.GetHeight() + kTextVerticalPadding * 2) {
+ set_focusable(true);
+ set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+ closed_icon_ = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ (base::i18n::IsRTL() ? IDR_FOLDER_CLOSED_RTL : IDR_FOLDER_CLOSED));
+ open_icon_ = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ (base::i18n::IsRTL() ? IDR_FOLDER_OPEN_RTL : IDR_FOLDER_OPEN));
+ text_offset_ = closed_icon_.width() + kImagePadding + kImagePadding +
+ kArrowRegionSize;
+}
+
+TreeView::~TreeView() {
+ if (model_)
+ model_->RemoveObserver(this);
+}
+
+View* TreeView::CreateParentIfNecessary() {
+ ScrollView* scroll_view = new ScrollView;
+ scroll_view->SetContents(this);
+ return scroll_view;
+}
+
+void TreeView::SetModel(TreeModel* model) {
+ if (model == model_)
+ return;
+ if (model_)
+ model_->RemoveObserver(this);
+
+ CancelEdit();
+
+ model_ = model;
+ selected_node_ = NULL;
+ icons_.clear();
+ if (model_) {
+ model_->AddObserver(this);
+ model_->GetIcons(&icons_);
+
+ root_.RemoveAll();
+ ConfigureInternalNode(model_->GetRoot(), &root_);
+ LoadChildren(&root_);
+ root_.set_is_expanded(true);
+ if (root_shown_)
+ selected_node_ = &root_;
+ else if (root_.child_count())
+ selected_node_ = root_.GetChild(0);
+ }
+ DrawnNodesChanged();
+}
+
+void TreeView::SetEditable(bool editable) {
+ if (editable == editable_)
+ return;
+ editable_ = editable;
+ CancelEdit();
+}
+
+void TreeView::StartEditing(TreeModelNode* node) {
+ DCHECK(node);
+ // Cancel the current edit.
+ CancelEdit();
+ // Make sure all ancestors are expanded.
+ if (model_->GetParent(node))
+ Expand(model_->GetParent(node));
+ // Select the node, else if the user commits the edit the selection reverts.
+ SetSelectedNode(node);
+ if (GetSelectedNode() != node)
+ return; // Selection failed for some reason, don't start editing.
+ DCHECK(!editor_);
+ editor_ = new Textfield;
+}
+
+void TreeView::CancelEdit() {
+ // TODO: remove editor.
+ editing_node_ = NULL;
+}
+
+void TreeView::CommitEdit() {
+ if (!editing_node_)
+ return;
+
+ // TODO: commit from textfield to model.
+}
+
+TreeModelNode* TreeView::GetEditingNode() {
+ return editing_node_ ? editing_node_->model_node() : NULL;
+}
+
+void TreeView::SetSelectedNode(TreeModelNode* model_node) {
+ if (model_node && model_->GetParent(model_node))
+ Expand(model_->GetParent(model_node));
+ if (model_node && model_node == root_.model_node() && !root_shown_)
+ return; // Ignore requests to select the root when not shown.
+ InternalNode* node = model_node ? GetInternalNodeForModelNode(
+ model_node, CREATE_IF_NOT_LOADED) : NULL;
+ bool was_empty_selection = (selected_node_ == NULL);
+ bool changed = (selected_node_ != node);
+ if (changed) {
+ SchedulePaintForNode(selected_node_);
+ selected_node_ = node;
+ if (selected_node_ == &root_ && !root_shown_)
+ selected_node_ = NULL;
+ if (selected_node_ && selected_node_ != &root_)
+ Expand(model_->GetParent(selected_node_->model_node()));
+ SchedulePaintForNode(selected_node_);
+ }
+
+ if (selected_node_)
+ ScrollRectToVisible(GetBoundsForNode(selected_node_));
+
+ // Notify controller if the old selection was empty to handle the case of
+ // remove explicitly resetting selected_node_ before invoking this.
+ if (controller_ && (changed || was_empty_selection))
+ controller_->OnTreeViewSelectionChanged(this);
+}
+
+TreeModelNode* TreeView::GetSelectedNode() {
+ return selected_node_ ? selected_node_->model_node() : NULL;
+}
+
+void TreeView::Collapse(ui::TreeModelNode* model_node) {
+ // Don't collapse the root if the root isn't shown, otherwise nothing is
+ // displayed.
+ if (model_node == root_.model_node() && !root_shown_)
+ return;
+ InternalNode* node =
+ GetInternalNodeForModelNode(model_node, DONT_CREATE_IF_NOT_LOADED);
+ if (!node)
+ return;
+ bool was_expanded = IsExpanded(model_node);
+ if (node->is_expanded()) {
+ if (editing_node_ && editing_node_->HasAncestor(node))
+ CancelEdit();
+ if (selected_node_ && selected_node_->HasAncestor(node))
+ SetSelectedNode(model_node);
+ node->set_is_expanded(false);
+ }
+ if (was_expanded)
+ DrawnNodesChanged();
+}
+
+void TreeView::Expand(TreeModelNode* node) {
+ if (ExpandImpl(node))
+ DrawnNodesChanged();
+ // TODO: need to support auto_expand_children_.
+}
+
+void TreeView::ExpandAll(TreeModelNode* node) {
+ DCHECK(node);
+ // Expand the node.
+ bool expanded_at_least_one = ExpandImpl(node);
+ // And recursively expand all the children.
+ for (int i = model_->GetChildCount(node) - 1; i >= 0; --i) {
+ TreeModelNode* child = model_->GetChild(node, i);
+ if (ExpandImpl(child))
+ expanded_at_least_one = true;
+ }
+ if (expanded_at_least_one)
+ DrawnNodesChanged();
+}
+
+bool TreeView::IsExpanded(TreeModelNode* model_node) {
+ if (!model_node) {
+ // NULL check primarily for convenience for uses in this class so don't have
+ // to add NULL checks every where we look up the parent.
+ return true;
+ }
+ InternalNode* node = GetInternalNodeForModelNode(
+ model_node, DONT_CREATE_IF_NOT_LOADED);
+ if (!node)
+ return false;
+
+ while (node) {
+ if (!node->is_expanded())
+ return false;
+ node = node->parent();
+ }
+ return true;
+}
+
+void TreeView::SetRootShown(bool root_shown) {
+ if (root_shown_ == root_shown)
+ return;
+ root_shown_ = root_shown;
+ if (!root_shown_ && selected_node_ == &root_) {
+ if (model_->GetChildCount(root_.model_node()))
+ SetSelectedNode(model_->GetChild(root_.model_node(), 0));
+ else
+ SetSelectedNode(NULL);
+ }
+ DrawnNodesChanged();
+}
+
+void TreeView::Layout() {
+ int width = preferred_size_.width();
+ int height = preferred_size_.height();
+ if (parent()) {
+ width = std::max(parent()->width(), width);
+ height = std::max(parent()->height(), height);
+ }
+ SetBounds(x(), y(), width, height);
+}
+
+gfx::Size TreeView::GetPreferredSize() {
+ return preferred_size_;
+}
+
+bool TreeView::OnMousePressed(const MouseEvent& event) {
+ int row = (event.y() - kVerticalInset) / row_height_;
+ int depth;
+ InternalNode* node = GetNodeByRow(row, &depth);
+ if (node) {
+ RequestFocus();
+ gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth));
+ if (bounds.Contains(event.location())) {
+ int relative_x = event.x() - bounds.x();
+ if (relative_x < kArrowRegionSize &&
+ model_->GetChildCount(node->model_node())) {
+ if (node->is_expanded())
+ Collapse(node->model_node());
+ else
+ Expand(node->model_node());
+ } else if (relative_x > kArrowRegionSize) {
+ SetSelectedNode(node->model_node());
+ if (event.flags() & ui::EF_IS_DOUBLE_CLICK) {
+ if (node->is_expanded())
+ Collapse(node->model_node());
+ else
+ Expand(node->model_node());
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void TreeView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_OUTLINE;
+ state->state = ui::AccessibilityTypes::STATE_READONLY;
+}
+
+void TreeView::TreeNodesAdded(TreeModel* model,
+ TreeModelNode* parent,
+ int start,
+ int count) {
+ InternalNode* parent_node =
+ GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED);
+ if (!parent_node || !parent_node->loaded_children())
+ return;
+ for (int i = 0; i < count; ++i) {
+ InternalNode* child = new InternalNode;
+ ConfigureInternalNode(model_->GetChild(parent, start + i), child);
+ parent_node->Add(child, start + i);
+ }
+ if (IsExpanded(parent))
+ DrawnNodesChanged();
+}
+
+void TreeView::TreeNodesRemoved(TreeModel* model,
+ TreeModelNode* parent,
+ int start,
+ int count) {
+ InternalNode* parent_node =
+ GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED);
+ if (!parent_node || !parent_node->loaded_children())
+ return;
+ bool reset_selection = false;
+ for (int i = 0; i < count; ++i) {
+ InternalNode* child_removing = parent_node->GetChild(start);
+ if (editing_node_ && editing_node_->HasAncestor(child_removing))
+ CancelEdit();
+ if (selected_node_ && selected_node_->HasAncestor(child_removing))
+ reset_selection = true;
+ delete parent_node->Remove(child_removing);
+ }
+ if (reset_selection) {
+ // selected_node_ is no longer valid (at the time we enter this function
+ // it's model_node() is likely deleted). Explicitly NULL out the field
+ // rather than invoking SetSelectedNode() otherwise, we'll try and use a
+ // deleted value.
+ selected_node_ = NULL;
+ TreeModelNode* to_select = parent;
+ if (parent == root_.model_node() && !root_shown_) {
+ to_select = model_->GetChildCount(parent) > 0 ?
+ model_->GetChild(parent, 0) : NULL;
+ }
+ SetSelectedNode(to_select);
+ }
+ if (IsExpanded(parent))
+ DrawnNodesChanged();
+}
+
+void TreeView::TreeNodeChanged(TreeModel* model, TreeModelNode* model_node) {
+ InternalNode* node =
+ GetInternalNodeForModelNode(model_node, DONT_CREATE_IF_NOT_LOADED);
+ if (!node)
+ return;
+ int old_width = node->text_width();
+ UpdateNodeTextWidth(node);
+ if (old_width != node->text_width() &&
+ ((node == &root_ && root_shown_) ||
+ (node != &root_ && IsExpanded(node->parent()->model_node())))) {
+ DrawnNodesChanged();
+ }
+}
+
+gfx::Point TreeView::GetKeyboardContextMenuLocation() {
+ int y = height() / 2;
+ if (selected_node_) {
+ gfx::Rect node_bounds(GetBoundsForNode(selected_node_));
+ gfx::Rect vis_bounds(GetVisibleBounds());
+ if (node_bounds.y() >= vis_bounds.y() &&
+ node_bounds.y() < vis_bounds.bottom()) {
+ y = node_bounds.y();
+ }
+ }
+ gfx::Point screen_loc(0, y);
+ if (base::i18n::IsRTL())
+ screen_loc.set_x(width());
+ ConvertPointToScreen(this, &screen_loc);
+ return screen_loc;
+}
+
+bool TreeView::OnKeyPressed(const KeyEvent& event) {
+ switch (event.key_code()) {
+ case ui::VKEY_F2:
+ if (!editing_node_) {
+ TreeModelNode* selected_node = GetSelectedNode();
+ if (selected_node && (!controller_ ||
+ controller_->CanEdit(this, selected_node))) {
+ StartEditing(selected_node);
+ }
+ }
+ return true;
+
+ case ui::VKEY_UP:
+ case ui::VKEY_DOWN:
+ IncrementSelection(event.key_code() == ui::VKEY_UP ?
+ INCREMENT_PREVIOUS : INCREMENT_NEXT);
+ return true;
+
+ case ui::VKEY_LEFT:
+ if (base::i18n::IsRTL())
+ ExpandOrSelectChild();
+ else
+ CollapseOrSelectParent();
+ return true;
+
+ case ui::VKEY_RIGHT:
+ if (base::i18n::IsRTL())
+ CollapseOrSelectParent();
+ else
+ ExpandOrSelectChild();
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+void TreeView::OnPaint(gfx::Canvas* canvas) {
+ // Don't invoke View::OnPaint so that we can render our own focus border.
+ OnPaintBackground(canvas);
+
+ int min_y, max_y;
+ {
+ SkRect sk_clip_rect;
+ if (canvas->GetSkCanvas()->getClipBounds(&sk_clip_rect)) {
+ gfx::Rect clip_rect = gfx::SkRectToRect(sk_clip_rect);
+ min_y = clip_rect.y();
+ max_y = clip_rect.bottom();
+ } else {
+ gfx::Rect vis_bounds = GetVisibleBounds();
+ min_y = vis_bounds.y();
+ max_y = vis_bounds.bottom();
+ }
+ }
+
+ int min_row = std::max(0, (min_y - kVerticalInset) / row_height_);
+ int max_row = (max_y - kVerticalInset) / row_height_;
+ if ((max_y - kVerticalInset) % row_height_ != 0)
+ max_row++;
+ int current_row = root_row();
+ PaintRows(canvas, min_row, max_row, &root_, root_depth(), &current_row);
+}
+
+void TreeView::OnFocus() {
+ SchedulePaintForNode(selected_node_);
+}
+
+void TreeView::OnBlur() {
+ SchedulePaintForNode(selected_node_);
+}
+
+void TreeView::LoadChildren(InternalNode* node) {
+ DCHECK_EQ(0, node->child_count());
+ DCHECK(!node->loaded_children());
+ node->set_loaded_children(true);
+ for (int i = 0, child_count = model_->GetChildCount(node->model_node());
+ i < child_count; ++i) {
+ InternalNode* child = new InternalNode;
+ ConfigureInternalNode(model_->GetChild(node->model_node(), i), child);
+ node->Add(child, node->child_count());
+ }
+}
+
+void TreeView::ConfigureInternalNode(TreeModelNode* model_node,
+ InternalNode* node) {
+ node->Reset(model_node);
+ UpdateNodeTextWidth(node);
+}
+
+void TreeView::UpdateNodeTextWidth(InternalNode* node) {
+ int width = 0, height = 0;
+ gfx::CanvasSkia::SizeStringInt(node->model_node()->GetTitle(),
+ font_, &width, &height, gfx::Canvas::NO_ELLIPSIS);
+ node->set_text_width(width);
+}
+
+void TreeView::DrawnNodesChanged() {
+ UpdatePreferredSize();
+ PreferredSizeChanged();
+ SchedulePaint();
+}
+
+void TreeView::UpdatePreferredSize() {
+ preferred_size_ = gfx::Size();
+ if (!model_)
+ return;
+
+ preferred_size_.SetSize(
+ root_.GetMaxWidth(text_offset_, root_shown_ ? 1 : 0) +
+ kTextHorizontalPadding * 2,
+ row_height_ * GetRowCount() + kVerticalInset * 2);
+}
+
+void TreeView::SchedulePaintForNode(InternalNode* node) {
+ if (!node)
+ return; // Explicitly allow NULL to be passed in.
+ SchedulePaintInRect(GetBoundsForNode(node));
+}
+
+void TreeView::PaintRows(gfx::Canvas* canvas,
+ int min_row,
+ int max_row,
+ InternalNode* node,
+ int depth,
+ int* row) {
+ if (*row >= max_row)
+ return;
+
+ if (*row >= min_row && *row < max_row)
+ PaintRow(canvas, node, *row, depth);
+ (*row)++;
+ if (!node->is_expanded())
+ return;
+ depth++;
+ for (int i = 0; i < node->child_count() && *row < max_row; ++i)
+ PaintRows(canvas, min_row, max_row, node->GetChild(i), depth, row);
+}
+
+void TreeView::PaintRow(gfx::Canvas* canvas,
+ InternalNode* node,
+ int row,
+ int depth) {
+ gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth));
+
+ if (model_->GetChildCount(node->model_node()))
+ PaintExpandControl(canvas, bounds, node->is_expanded());
+
+ // Paint the icon.
+ SkBitmap icon;
+ int icon_index = model_->GetIconIndex(node->model_node());
+ if (icon_index != -1)
+ icon = icons_[icon_index];
+ else if (node == selected_node_)
+ icon = open_icon_;
+ else
+ icon = closed_icon_;
+ canvas->DrawBitmapInt(
+ icon, bounds.x() + kArrowRegionSize + kImagePadding +
+ (open_icon_.width() - icon.width()) / 2,
+ bounds.y() + (bounds.height() - icon.height()) / 2);
+
+ if (node != editing_node_) {
+ gfx::Rect text_bounds(bounds.x() + text_offset_, bounds.y(),
+ bounds.width() - text_offset_, bounds.height());
+ if (node == selected_node_) {
+ canvas->FillRect(kSelectedBackgroundColor, text_bounds);
+ if (HasFocus())
+ canvas->DrawFocusRect(text_bounds);
+ }
+ canvas->DrawStringInt(node->model_node()->GetTitle(), font_, kTextColor,
+ text_bounds.x() + kTextHorizontalPadding,
+ text_bounds.y() + kTextVerticalPadding,
+ text_bounds.width() - kTextHorizontalPadding * 2,
+ text_bounds.height() - kTextVerticalPadding * 2);
+ }
+}
+
+void TreeView::PaintExpandControl(gfx::Canvas* canvas,
+ const gfx::Rect& node_bounds,
+ bool expanded) {
+ int center_x = node_bounds.x() + (kArrowRegionSize - 4) / 2;
+ int center_y = node_bounds.y() + node_bounds.height() / 2;
+ // TODO: this should come from an image.
+ if (!expanded) {
+ for (int i = 0; i < 4; ++i) {
+ canvas->FillRect(kArrowColor,
+ gfx::Rect(center_x - (2 - i), center_y - (3 - i), 1,
+ (3 - i) * 2 + 1));
+ }
+ } else {
+ center_y -= 2;
+ for (int i = 0; i < 4; ++i) {
+ canvas->FillRect(kArrowColor,
+ gfx::Rect(center_x - (3 - i), center_y + i,
+ (3 - i) * 2 + 1, 1));
+ }
+ }
+}
+
+TreeView::InternalNode* TreeView::GetInternalNodeForModelNode(
+ ui::TreeModelNode* model_node,
+ GetInternalNodeCreateType create_type) {
+ if (model_node == root_.model_node())
+ return &root_;
+ InternalNode* parent_internal_node =
+ GetInternalNodeForModelNode(model_->GetParent(model_node), create_type);
+ if (!parent_internal_node)
+ return NULL;
+ if (!parent_internal_node->loaded_children()) {
+ if (create_type == DONT_CREATE_IF_NOT_LOADED)
+ return NULL;
+ LoadChildren(parent_internal_node);
+ }
+ return parent_internal_node->GetChild(
+ model_->GetIndexOf(parent_internal_node->model_node(), model_node));
+}
+
+gfx::Rect TreeView::GetBoundsForNode(InternalNode* node) {
+ int row, depth;
+ row = GetRowForNode(node, &depth);
+ return GetBoundsForNodeImpl(node, row, depth);
+}
+
+gfx::Rect TreeView::GetBoundsForNodeImpl(InternalNode* node,
+ int row,
+ int depth) {
+ return gfx::Rect(depth * kIndent + kHorizontalInset,
+ row * row_height_ + kVerticalInset,
+ text_offset_ + node->text_width() +
+ kTextHorizontalPadding * 2,
+ row_height_);
+}
+
+int TreeView::GetRowCount() {
+ int row_count = root_.NumExpandedNodes();
+ if (!root_shown_)
+ row_count--;
+ return row_count;
+}
+
+int TreeView::GetRowForNode(InternalNode* node, int* depth) {
+ DCHECK(!node->parent() || IsExpanded(node->parent()->model_node()));
+ *depth = -1;
+ int row = -1;
+ InternalNode* tmp_node = node;
+ while (tmp_node->parent()) {
+ int index_in_parent = tmp_node->parent()->GetIndexOf(tmp_node);
+ (*depth)++;
+ row++; // For node.
+ for (int i = 0; i < index_in_parent; ++i)
+ row += tmp_node->parent()->GetChild(i)->NumExpandedNodes();
+ tmp_node = tmp_node->parent();
+ }
+ if (root_shown_) {
+ (*depth)++;
+ row++;
+ }
+ return row;
+}
+
+TreeView::InternalNode* TreeView::GetNodeByRow(int row, int* depth) {
+ int current_row = root_row();
+ *depth = 0;
+ return GetNodeByRowImpl(&root_, row, root_depth(), &current_row, depth);
+}
+
+TreeView::InternalNode* TreeView::GetNodeByRowImpl(InternalNode* node,
+ int target_row,
+ int current_depth,
+ int* current_row,
+ int* node_depth) {
+ if (*current_row == target_row) {
+ *node_depth = current_depth;
+ return node;
+ }
+ (*current_row)++;
+ if (node->is_expanded()) {
+ current_depth++;
+ for (int i = 0; i < node->child_count(); ++i) {
+ InternalNode* result = GetNodeByRowImpl(
+ node->GetChild(i), target_row, current_depth, current_row,
+ node_depth);
+ if (result)
+ return result;
+ }
+ }
+ return NULL;
+}
+
+void TreeView::IncrementSelection(IncrementType type) {
+ if (!model_)
+ return;
+
+ if (!GetSelectedNode()) {
+ // If nothing is selected select the first or last node.
+ if (!root_.child_count())
+ return;
+ if (type == INCREMENT_PREVIOUS) {
+ int row_count = GetRowCount();
+ int depth;
+ DCHECK(row_count);
+ InternalNode* node = GetNodeByRow(row_count - 1, &depth);
+ SetSelectedNode(node->model_node());
+ } else if (root_shown_) {
+ SetSelectedNode(root_.model_node());
+ } else {
+ SetSelectedNode(root_.GetChild(0)->model_node());
+ }
+ return;
+ }
+
+ int row, depth;
+ int delta = type == INCREMENT_PREVIOUS ? -1 : 1;
+ row = GetRowForNode(selected_node_, &depth);
+ int new_row = std::min(GetRowCount() - 1, std::max(0, row + delta));
+ if (new_row == row)
+ return; // At the end/beginning.
+ SetSelectedNode(GetNodeByRow(new_row, &depth)->model_node());
+}
+
+void TreeView::CollapseOrSelectParent() {
+ if (selected_node_) {
+ if (selected_node_->is_expanded())
+ Collapse(selected_node_->model_node());
+ else if (selected_node_->parent())
+ SetSelectedNode(selected_node_->parent()->model_node());
+ }
+}
+
+void TreeView::ExpandOrSelectChild() {
+ if (selected_node_) {
+ if (!selected_node_->is_expanded())
+ Expand(selected_node_->model_node());
+ else if (selected_node_->child_count())
+ SetSelectedNode(selected_node_->GetChild(0)->model_node());
+ }
+}
+
+bool TreeView::ExpandImpl(TreeModelNode* model_node) {
+ TreeModelNode* parent = model_->GetParent(model_node);
+ if (!parent) {
+ // Node should be the root.
+ DCHECK_EQ(root_.model_node(), model_node);
+ bool was_expanded = root_.is_expanded();
+ root_.set_is_expanded(true);
+ return !was_expanded;
+ }
+
+ // Expand all the parents.
+ bool return_value = ExpandImpl(parent);
+ InternalNode* internal_node =
+ GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED);
+ DCHECK(internal_node);
+ if (!internal_node->is_expanded()) {
+ if (!internal_node->loaded_children())
+ LoadChildren(internal_node);
+ internal_node->set_is_expanded(true);
+ return_value = true;
+ }
+ return return_value;
+}
+
+// InternalNode ----------------------------------------------------------------
+
+TreeView::InternalNode::InternalNode()
+ : model_node_(NULL),
+ loaded_children_(false),
+ is_expanded_(false),
+ text_width_(0) {
+}
+
+TreeView::InternalNode::~InternalNode() {
+}
+
+void TreeView::InternalNode::Reset(ui::TreeModelNode* node) {
+ model_node_ = node;
+ loaded_children_ = false;
+ is_expanded_ = false;
+ text_width_ = 0;
+}
+
+int TreeView::InternalNode::NumExpandedNodes() {
+ int result = 1; // For this.
+ if (!is_expanded_)
+ return result;
+ for (int i = 0; i < child_count(); ++i)
+ result += GetChild(i)->NumExpandedNodes();
+ return result;
+}
+
+int TreeView::InternalNode::GetMaxWidth(int indent, int depth) {
+ int max_width = text_width_ + indent * depth;
+ if (!is_expanded_)
+ return max_width;
+ for (int i = 0; i < child_count(); ++i) {
+ max_width = std::max(max_width,
+ GetChild(i)->GetMaxWidth(indent, depth + 1));
+ }
+ return max_width;
+}
+
+} // namespace views
diff --git a/ui/views/controls/tree/tree_view_views.h b/ui/views/controls/tree/tree_view_views.h
index b778812..de621ec 100644
--- a/ui/views/controls/tree/tree_view_views.h
+++ b/ui/views/controls/tree/tree_view_views.h
@@ -2,34 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef UI_VIEWS_CONTROLS_TREE_TREE_VIEW_H_
-#define UI_VIEWS_CONTROLS_TREE_TREE_VIEW_H_
+#ifndef UI_VIEWS_CONTROLS_TREE_TREE_VIEW_VIEWS_H_
+#define UI_VIEWS_CONTROLS_TREE_TREE_VIEW_VIEWS_H_
#pragma once
-#include <windows.h>
-#include <commctrl.h>
-
-#include <map>
+#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
-#include "ui/base/keycodes/keyboard_codes.h"
-#include "ui/base/models/tree_model.h"
-#include "ui/views/controls/native_control.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/models/tree_node_model.h"
+#include "ui/gfx/font.h"
+#include "ui/views/view.h"
namespace views {
+class Textfield;
+class TreeViewController;
+
// 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 VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
+//
+// Note on implementation. This implementation doesn't scale well. In particular
+// it does not store any row information, but instead calculates it as
+// necessary. But it's more than adequate for current uses.
+class VIEWS_EXPORT TreeView : public View, public ui::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_; }
+ // Returns new ScrollPane that contains the receiver.
+ View* CreateParentIfNecessary();
// Sets the model. TreeView does not take ownership of the model.
void SetModel(ui::TreeModel* model);
@@ -48,19 +52,11 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
// the Controller is queried to determine if a particular node can be edited.
void SetEditable(bool editable);
- // Sets whether lines are drawn from the root node to child nodes (and
- // whether plus boxes show up next to the root node.) The default is false.
- // If root_shown_ is false, the children of the root act as the roots in the
- // native control, and so this setting takes effect for them.
- void set_lines_at_root(bool lines_at_root) {
- lines_at_root_ = lines_at_root;
- }
-
- // Overridden from View:
- virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ // Does nothing, but required for compatibility with Windows implementation.
+ void set_lines_at_root(bool lines_at_root) {}
- // Edits the specified node. This cancels the current edit and expands
- // all parents of node.
+ // Edits the specified node. This cancels the current edit and expands all
+ // parents of node.
void StartEditing(ui::TreeModelNode* node);
// Cancels the current edit. Does nothing if not editing.
@@ -74,11 +70,15 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
ui::TreeModelNode* GetEditingNode();
// Selects the specified node. This expands all the parents of node.
- void SetSelectedNode(ui::TreeModelNode* node);
+ void SetSelectedNode(ui::TreeModelNode* model_node);
// Returns the selected node, or NULL if nothing is selected.
ui::TreeModelNode* GetSelectedNode();
+ // Marks |model_node| as collapsed. This only effects the UI if node and all
+ // it's parents are expanded (IsExpanded(model_node) returns true).
+ void Collapse(ui::TreeModelNode* model_node);
+
// Make sure node and all its parents are expanded.
void Expand(ui::TreeModelNode* node);
@@ -87,16 +87,26 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
void ExpandAll(ui::TreeModelNode* node);
// Returns true if the specified node is expanded.
- bool IsExpanded(ui::TreeModelNode* node);
+ bool IsExpanded(ui::TreeModelNode* model_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);
- // Begin TreeModelObserver implementation.
- // These methods shouldn't be called directly. The model is responsible for
- // firing them.
+ // Sets the controller, which may be null. TreeView does not take ownership
+ // of the controller.
+ void SetController(TreeViewController* controller) {
+ controller_ = controller;
+ }
+
+ // View overrides:
+ virtual void Layout() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // TreeModelObserver overrides:
virtual void TreeNodesAdded(ui::TreeModel* model,
ui::TreeModelNode* parent,
int start,
@@ -106,163 +116,196 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
int start,
int count) OVERRIDE;
virtual void TreeNodeChanged(ui::TreeModel* model,
- ui::TreeModelNode* node) OVERRIDE;
- // End TreeModelObserver implementation.
+ ui::TreeModelNode* model_node) OVERRIDE;
- // Sets the controller, which may be null. TreeView does not take ownership
- // of the controller.
- void SetController(TreeViewController* controller) {
- controller_ = controller;
- }
+ protected:
+ // View overrides:
+ virtual gfx::Point GetKeyboardContextMenuLocation() OVERRIDE;
+ virtual bool OnKeyPressed(const KeyEvent& event) OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
- // 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_; }
+ private:
+ friend class TreeViewViewsTest;
- // 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_;
- }
+ // InternalNode is used to track information about the set of nodes displayed
+ // by TreeViewViews.
+ class InternalNode : public ui::TreeNode<InternalNode> {
+ public:
+ InternalNode();
+ virtual ~InternalNode();
- // 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_; }
+ // Resets the state from |node|.
+ void Reset(ui::TreeModelNode* node);
- protected:
- // Overriden to return a location based on the selected node.
- virtual gfx::Point GetKeyboardContextMenuLocation();
+ // The model node this InternalNode represents.
+ ui::TreeModelNode* model_node() { return model_node_; }
- // Creates and configures the tree_view.
- virtual HWND CreateNativeControl(HWND parent_container);
+ // Whether the node is expanded.
+ void set_is_expanded(bool expanded) { is_expanded_ = expanded; }
+ bool is_expanded() const { return is_expanded_; }
- // 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);
+ // Whether children have been loaded.
+ void set_loaded_children(bool value) { loaded_children_ = value; }
+ bool loaded_children() const { return loaded_children_; }
- // Invoked when the native control send a WM_DESTROY message to its parent.
- virtual void OnDestroy();
+ // Width needed to display the string.
+ void set_text_width(int width) { text_width_ = width; }
+ int text_width() const { return text_width_; }
- // We pay attention to 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 OnKeyDown(ui::KeyboardCode virtual_key_code);
+ // Returns the total number of descendants (including this node).
+ int NumExpandedNodes();
- virtual void OnContextMenu(const POINT& location);
+ // Returns the max width of all descendants (including this node). |indent|
+ // is how many pixels each child is indented and |depth| is the depth of
+ // this node from it's parent.
+ int GetMaxWidth(int indent, int depth);
- // Returns the TreeModelNode for |tree_item|.
- ui::TreeModelNode* GetNodeForTreeItem(HTREEITEM tree_item);
+ private:
+ // The node from the model.
+ ui::TreeModelNode* model_node_;
- // Returns the tree item for |node|.
- HTREEITEM GetTreeItemForNode(ui::TreeModelNode* node);
+ // Whether the children have been loaded.
+ bool loaded_children_;
- private:
- // See notes in TableView::TableViewWrapper for why this is needed.
- struct TreeViewWrapper {
- explicit TreeViewWrapper(TreeView* view) : tree_view(view) { }
- TreeView* tree_view;
- };
+ bool is_expanded_;
+
+ int text_width_;
- // Internally used to track the state of nodes. NodeDetails are lazily created
- // as the user expands nodes.
- struct NodeDetails {
- NodeDetails(int id, ui::TreeModelNode* node)
- : id(id), node(node), tree_item(NULL), loaded_children(false) {}
+ DISALLOW_COPY_AND_ASSIGN(InternalNode);
+ };
- // Unique identifier for the node. This corresponds to the lParam of
- // the tree item.
- const int id;
+ // Used by GetInternalNodeForModelNode.
+ enum GetInternalNodeCreateType {
+ // If an InternalNode hasn't been created yet, create it.
+ CREATE_IF_NOT_LOADED,
- // The node from the model.
- ui::TreeModelNode* node;
+ // Don't create an InternalNode if one hasn't been created yet.
+ DONT_CREATE_IF_NOT_LOADED,
+ };
- // From the native TreeView.
- //
- // This should be treated as const, but can't due to timing in creating the
- // entry.
- HTREEITEM tree_item;
+ // Used by IncrementSelection.
+ enum IncrementType {
+ // Selects the next node.
+ INCREMENT_NEXT,
- // Whether the children have been loaded.
- bool loaded_children;
+ // Selects the previous node.
+ INCREMENT_PREVIOUS
};
- // Cleanup all resources used by treeview.
- void Cleanup();
+ // Row of the root node. This varies depending upon whether the root is
+ // visible.
+ int root_row() const { return root_shown_ ? 0 : -1; }
+
+ // Depth of the root node.
+ int root_depth() const { return root_shown_ ? 0 : -1; }
+
+ // Loads the children of the specified node.
+ void LoadChildren(InternalNode* node);
- // Make sure the tree view is observing the tree model.
- void AddObserverToModel();
+ // Configures an InternalNode from a node from the model. This is used
+ // when a node changes as well as when loading.
+ void ConfigureInternalNode(ui::TreeModelNode* model_node, InternalNode* node);
- // Make sure the tree view is no longer observing the tree model.
- void RemoveObserverFromModel();
+ // Sets |node|s text_width.
+ void UpdateNodeTextWidth(InternalNode* node);
- // Deletes the root items from the treeview. This is used when the model
- // changes.
- void DeleteRootItems();
+ // Invoked when the set of drawn nodes changes.
+ void DrawnNodesChanged();
- // Creates the root items in the treeview from the model. This is used when
- // the model changes.
- void CreateRootItems();
+ // Updates |preferred_size_| from the state of the UI.
+ void UpdatePreferredSize();
- // 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,
- ui::TreeModelNode* node);
+ // Schedules a paint for |node|.
+ void SchedulePaintForNode(InternalNode* 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);
+ // Recursively paints rows from |min_row| to |max_row|. |node| is the node for
+ // the row |*row|. |row| is updated as this walks the tree. Depth is the depth
+ // of |*row|.
+ void PaintRows(gfx::Canvas* canvas,
+ int min_row,
+ int max_row,
+ InternalNode* node,
+ int depth,
+ int* row);
- // Returns the NodeDetails by node from the model.
- NodeDetails* GetNodeDetails(ui::TreeModelNode* node);
+ // Invoked to paint a single node.
+ void PaintRow(gfx::Canvas* canvas,
+ InternalNode* node,
+ int row,
+ int depth);
- // Returns the NodeDetails by identifier (lparam of the HTREEITEM).
- NodeDetails* GetNodeDetailsByID(int id);
+ // Paints the expand control given the specified nodes bounds.
+ void PaintExpandControl(gfx::Canvas* canvas,
+ const gfx::Rect& node_bounds,
+ bool expanded);
- // Returns the NodeDetails by HTREEITEM.
- NodeDetails* GetNodeDetailsByTreeItem(HTREEITEM tree_item);
+ // Returns the InternalNode for a model node. |create_type| indicates wheter
+ // this should load InternalNode or not.
+ InternalNode* GetInternalNodeForModelNode(
+ ui::TreeModelNode* model_node,
+ GetInternalNodeCreateType create_type);
- // Creates the image list to use for the tree.
- HIMAGELIST CreateImageList();
+ // Returns the bounds for a node.
+ gfx::Rect GetBoundsForNode(InternalNode* node);
- // 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(ui::TreeModelNode* node);
+ // Implementation of GetBoundsForNode. Separated out as some callers already
+ // know the row/depth.
+ gfx::Rect GetBoundsForNodeImpl(InternalNode* node, int row, int depth);
- // The window function installed on the treeview.
- static LRESULT CALLBACK TreeWndProc(HWND window,
- UINT message,
- WPARAM w_param,
- LPARAM l_param);
+ // Returns the number of rows.
+ int GetRowCount();
- // Handle to the tree window.
- HWND tree_view_;
+ // Returns the row and depth of a node.
+ int GetRowForNode(InternalNode* node, int* depth);
+
+ // Returns the row and depth of the specified node.
+ InternalNode* GetNodeByRow(int row, int* depth);
+
+ // Implementation of GetNodeByRow. |curent_row| is updated as we iterate.
+ InternalNode* GetNodeByRowImpl(InternalNode* node,
+ int target_row,
+ int current_depth,
+ int* current_row,
+ int* node_depth);
+
+ // Increments the selection. Invoked in response to up/down arrow.
+ void IncrementSelection(IncrementType type);
+
+ // If the current node is expanded, it's collapsed, otherwise selection is
+ // moved to the parent.
+ void CollapseOrSelectParent();
+
+ // If the selected node is collapsed, it's expanded. Otherwise the first child
+ // is seleected.
+ void ExpandOrSelectChild();
+
+ // Implementation of Expand(). Returns true if at least one node was expanded
+ // that previously wasn't.
+ bool ExpandImpl(ui::TreeModelNode* model_node);
// The model, may be null.
ui::TreeModel* model_;
- // Maps from id to NodeDetails.
- std::map<int, NodeDetails*> id_to_details_map_;
+ // Default icons for closed/open.
+ SkBitmap closed_icon_;
+ SkBitmap open_icon_;
+
+ // Icons from the model.
+ std::vector<SkBitmap> icons_;
- // Maps from model entry to NodeDetails.
- std::map<ui::TreeModelNode*, NodeDetails*> node_to_details_map_;
+ // The root node.
+ InternalNode root_;
+
+ // The selected node, may be NULL.
+ InternalNode* selected_node_;
+
+ // Node users is editing, NULL if not editing.
+ InternalNode* editing_node_;
+
+ // Used when editing.
+ Textfield* editor_;
// Whether to automatically expand children when a parent node is expanded.
bool auto_expand_children_;
@@ -270,50 +313,31 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
// 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.
- ui::TreeModelNode* editing_node_;
-
// Whether or not the root is shown in the tree.
bool root_shown_;
- // Whether lines are drawn from the root to the children.
- bool lines_at_root_;
-
- // 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_;
+ // Did the model return a non-empty set of icons from GetIcons?
+ bool has_custom_icons_;
- bool drag_enabled_;
+ // Cached preferred size.
+ gfx::Size preferred_size_;
- // Has an observer been added to the model?
- bool observer_added_;
+ // Font used to display text.
+ gfx::Font font_;
- // Did the model return a non-empty set of icons from GetIcons?
- bool has_custom_icons_;
+ // Height of each row. Based on font and some padding.
+ int row_height_;
- HIMAGELIST image_list_;
+ // Offset the text is drawn at. This accounts for the size of the expand
+ // control, icon and offsets.
+ int text_offset_;
DISALLOW_COPY_AND_ASSIGN(TreeView);
};
} // namespace views
-#endif // UI_VIEWS_CONTROLS_TREE_TREE_VIEW_H_
+#endif // UI_VIEWS_CONTROLS_TREE_TREE_VIEW_VIEWS_H_
diff --git a/ui/views/controls/tree/tree_view_views_unittest.cc b/ui/views/controls/tree/tree_view_views_unittest.cc
new file mode 100644
index 0000000..281e105
--- /dev/null
+++ b/ui/views/controls/tree/tree_view_views_unittest.cc
@@ -0,0 +1,384 @@
+// Copyright (c) 2012 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 "ui/views/controls/tree/tree_view_views.h"
+
+#include <string>
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/models/tree_node_model.h"
+#include "ui/views/test/views_test_base.h"
+
+using ui::TreeModel;
+using ui::TreeModelNode;
+using ui::TreeNode;
+
+namespace views {
+
+class TestNode : public TreeNode<TestNode> {
+ public:
+ TestNode() {}
+ virtual ~TestNode() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestNode);
+};
+
+// Creates the following structure:
+// 'root'
+// 'a'
+// 'b'
+// 'b1'
+// 'c'
+class TreeViewViewsTest : public ViewsTestBase {
+ public:
+ TreeViewViewsTest() : model_(new TestNode) {
+ static_cast<TestNode*>(model_.GetRoot())->SetTitle(ASCIIToUTF16("root"));
+ Add(model_.GetRoot(), 0, "a");
+ Add(Add(model_.GetRoot(), 1, "b"), 0, "b1");
+ Add(model_.GetRoot(), 2, "c");
+ }
+
+ protected:
+ TestNode* Add(TestNode* parent,
+ int index,
+ const std::string& title);
+
+ std::string TreeViewContentsAsString();
+
+ std::string GetSelectedNodeTitle();
+
+ std::string GetEditingNodeTitle();
+
+ TestNode* GetNodeByTitle(const std::string& title);
+
+ void IncrementSelection(bool next);
+ void CollapseOrSelectParent();
+ void ExpandOrSelectChild();
+ int GetRowCount();
+
+ ui::TreeNodeModel<TestNode > model_;
+ TreeView tree_;
+
+ private:
+ std::string InternalNodeAsString(TreeView::InternalNode* node);
+
+ TestNode* GetNodeByTitleImpl(TestNode* node, const string16& title);
+
+ DISALLOW_COPY_AND_ASSIGN(TreeViewViewsTest);
+};
+
+TestNode* TreeViewViewsTest::Add(TestNode* parent,
+ int index,
+ const std::string& title) {
+ TestNode* new_node = new TestNode;
+ new_node->SetTitle(ASCIIToUTF16(title));
+ model_.Add(parent, new_node, index);
+ return new_node;
+}
+
+std::string TreeViewViewsTest::TreeViewContentsAsString() {
+ return InternalNodeAsString(&tree_.root_);
+}
+
+std::string TreeViewViewsTest::GetSelectedNodeTitle() {
+ TreeModelNode* model_node = tree_.GetSelectedNode();
+ return model_node ? UTF16ToASCII(model_node->GetTitle()) : std::string();
+}
+
+std::string TreeViewViewsTest::GetEditingNodeTitle() {
+ TreeModelNode* model_node = tree_.GetEditingNode();
+ return model_node ? UTF16ToASCII(model_node->GetTitle()) : std::string();
+}
+
+TestNode* TreeViewViewsTest::GetNodeByTitle(const std::string& title) {
+ return GetNodeByTitleImpl(model_.GetRoot(), ASCIIToUTF16(title));
+}
+
+void TreeViewViewsTest::IncrementSelection(bool next) {
+ tree_.IncrementSelection(next ? TreeView::INCREMENT_NEXT :
+ TreeView::INCREMENT_PREVIOUS);
+}
+
+void TreeViewViewsTest::CollapseOrSelectParent() {
+ tree_.CollapseOrSelectParent();
+}
+
+void TreeViewViewsTest::ExpandOrSelectChild() {
+ tree_.ExpandOrSelectChild();
+}
+
+int TreeViewViewsTest::GetRowCount() {
+ return tree_.GetRowCount();
+}
+
+TestNode* TreeViewViewsTest::GetNodeByTitleImpl(TestNode* node,
+ const string16& title) {
+ if (node->GetTitle() == title)
+ return node;
+ for (int i = 0; i < node->child_count(); ++i) {
+ TestNode* child = GetNodeByTitleImpl(node->GetChild(i), title);
+ if (child)
+ return child;
+ }
+ return NULL;
+}
+
+std::string TreeViewViewsTest::InternalNodeAsString(
+ TreeView::InternalNode* node) {
+ std::string result = UTF16ToASCII(node->model_node()->GetTitle());
+ if (node->is_expanded() && node->child_count()) {
+ result += " [";
+ for (int i = 0; i < node->child_count(); ++i) {
+ if (i > 0)
+ result += " ";
+ result += InternalNodeAsString(node->GetChild(i));
+ }
+ result += "]";
+ }
+ return result;
+}
+
+// Verifies setting model correctly updates internal state.
+TEST_F(TreeViewViewsTest, SetModel) {
+ tree_.SetModel(&model_);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+}
+
+// Verifies SetSelectedNode works.
+TEST_F(TreeViewViewsTest, SetSelectedNode) {
+ tree_.SetModel(&model_);
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+
+ // NULL should clear the selection.
+ tree_.SetSelectedNode(NULL);
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+
+ // Select 'c'.
+ tree_.SetSelectedNode(GetNodeByTitle("c"));
+ EXPECT_EQ("c", GetSelectedNodeTitle());
+
+ // Select 'b1', which should expand 'b'.
+ tree_.SetSelectedNode(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+}
+
+// Makes sure SetRootShown doesn't blow up.
+TEST_F(TreeViewViewsTest, HideRoot) {
+ tree_.SetModel(&model_);
+ tree_.SetRootShown(false);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ(3, GetRowCount());
+}
+
+// Expands a node and verifies the children are loaded correctly.
+TEST_F(TreeViewViewsTest, Expand) {
+ tree_.SetModel(&model_);
+ tree_.Expand(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("root",GetSelectedNodeTitle());
+ EXPECT_EQ(5, GetRowCount());
+}
+
+// Collapes a node and verifies state.
+TEST_F(TreeViewViewsTest, Collapse) {
+ tree_.SetModel(&model_);
+ tree_.Expand(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ(5, GetRowCount());
+ tree_.SetSelectedNode(GetNodeByTitle("b1"));
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+ tree_.Collapse(GetNodeByTitle("b"));
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ // Selected node should have moved to 'b'
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+}
+
+// Verifies adding nodes works.
+TEST_F(TreeViewViewsTest, TreeNodesAdded) {
+ tree_.SetModel(&model_);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ // Add a node between b and c.
+ Add(model_.GetRoot(), 2, "B");
+ EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(5, GetRowCount());
+
+ // Add a child of b1, which hasn't been loaded and shouldn't do anything.
+ Add(GetNodeByTitle("b1"), 0, "b11");
+ EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(5, GetRowCount());
+
+ // Add a child of b, which isn't expanded yet, so it shouldn't effect
+ // anything.
+ Add(GetNodeByTitle("b"), 1, "b2");
+ EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(5, GetRowCount());
+
+ // Expand b and make sure b2 is there.
+ tree_.Expand(GetNodeByTitle("b"));
+ EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewContentsAsString());
+ EXPECT_EQ("root",GetSelectedNodeTitle());
+ EXPECT_EQ(7, GetRowCount());
+}
+
+// Verifies removing nodes works.
+TEST_F(TreeViewViewsTest, TreeNodesRemoved) {
+ // Add c1 as a child of c and c11 as a child of c1.
+ Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
+ tree_.SetModel(&model_);
+
+ // Remove c11, which shouldn't have any effect on the tree.
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+
+ // Expand b1, then collapse it and remove it's only child, b1. This shouldn't
+ // effect the tree.
+ tree_.Expand(GetNodeByTitle("b"));
+ tree_.Collapse(GetNodeByTitle("b"));
+ model_.Remove(GetNodeByTitle("b1")->parent(), GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+
+ // Remove 'b'.
+ model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
+ EXPECT_EQ("root [a c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(3, GetRowCount());
+
+ // Remove 'c11', shouldn't visually change anything.
+ model_.Remove(GetNodeByTitle("c11")->parent(), GetNodeByTitle("c11"));
+ EXPECT_EQ("root [a c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(3, GetRowCount());
+
+ // Select 'c1', remove 'c' and make sure selection changes.
+ tree_.SetSelectedNode(GetNodeByTitle("c1"));
+ EXPECT_EQ("c1", GetSelectedNodeTitle());
+ model_.Remove(GetNodeByTitle("c")->parent(), GetNodeByTitle("c"));
+ EXPECT_EQ("root [a]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(2, GetRowCount());
+
+ tree_.SetRootShown(false);
+ // Add 'b' select it and remove it. Because we're not showing the root
+ // selection should change to 'a'.
+ Add(GetNodeByTitle("root"), 1, "b");
+ tree_.SetSelectedNode(GetNodeByTitle("b"));
+ model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
+ EXPECT_EQ("root [a]", TreeViewContentsAsString());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ(1, GetRowCount());
+}
+
+// Verifies changing a node title works.
+TEST_F(TreeViewViewsTest, TreeNodeChanged) {
+ // Add c1 as a child of c and c11 as a child of c1.
+ Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
+ tree_.SetModel(&model_);
+
+ // Change c11, shouldn't do anything.
+ model_.SetTitle(GetNodeByTitle("c11"), ASCIIToUTF16("c11.new"));
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+
+ // Change 'b1', shouldn't do anything.
+ model_.SetTitle(GetNodeByTitle("b1"), ASCIIToUTF16("b1.new"));
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+
+ // Change 'b'.
+ model_.SetTitle(GetNodeByTitle("b"), ASCIIToUTF16("b.new"));
+ EXPECT_EQ("root [a b.new c]", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ(4, GetRowCount());
+}
+
+// Verifies IncrementSelection() works.
+TEST_F(TreeViewViewsTest, IncrementSelection) {
+ tree_.SetModel(&model_);
+
+ IncrementSelection(true);
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ IncrementSelection(true);
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ IncrementSelection(true);
+ tree_.Expand(GetNodeByTitle("b"));
+ IncrementSelection(false);
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+ IncrementSelection(true);
+ EXPECT_EQ("c", GetSelectedNodeTitle());
+ IncrementSelection(true);
+ EXPECT_EQ("c", GetSelectedNodeTitle());
+
+ tree_.SetRootShown(false);
+ tree_.SetSelectedNode(GetNodeByTitle("a"));
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ IncrementSelection(false);
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+}
+
+// Verifies CollapseOrSelectParent works.
+TEST_F(TreeViewViewsTest, CollapseOrSelectParent) {
+ tree_.SetModel(&model_);
+
+ tree_.SetSelectedNode(GetNodeByTitle("root"));
+ CollapseOrSelectParent();
+ EXPECT_EQ("root", TreeViewContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+
+ // Hide the root, which should implicitly expand the root.
+ tree_.SetRootShown(false);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+
+ tree_.SetSelectedNode(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+ CollapseOrSelectParent();
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ CollapseOrSelectParent();
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+}
+
+// Verifies ExpandOrSelectChild works.
+TEST_F(TreeViewViewsTest, ExpandOrSelectChild) {
+ tree_.SetModel(&model_);
+
+ tree_.SetSelectedNode(GetNodeByTitle("root"));
+ ExpandOrSelectChild();
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+
+ ExpandOrSelectChild();
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+
+ tree_.SetSelectedNode(GetNodeByTitle("b"));
+ ExpandOrSelectChild();
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ ExpandOrSelectChild();
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+ ExpandOrSelectChild();
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("b1", GetSelectedNodeTitle());
+}
+
+} // namespace views
diff --git a/ui/views/controls/tree/tree_view_win.cc b/ui/views/controls/tree/tree_view_win.cc
index 9e53007..33d271a 100644
--- a/ui/views/controls/tree/tree_view_win.cc
+++ b/ui/views/controls/tree/tree_view_win.cc
@@ -57,6 +57,10 @@ TreeView::~TreeView() {
Cleanup();
}
+View* TreeView::CreateParentIfNecessary() {
+ return this;
+}
+
void TreeView::GetAccessibleState(ui::AccessibleViewState* state) {
state->role = ui::AccessibilityTypes::ROLE_OUTLINE;
state->state = ui::AccessibilityTypes::STATE_READONLY;
@@ -446,15 +450,6 @@ LRESULT TreeView::OnNotify(int w_param, LPNMHDR l_param) {
return 0;
}
- case TVN_KEYDOWN:
- if (controller_) {
- NMTVKEYDOWN* key_down_message =
- reinterpret_cast<NMTVKEYDOWN*>(l_param);
- controller_->OnTreeViewKeyDown(
- ui::KeyboardCodeForWindowsKeyCode(key_down_message->wVKey));
- }
- break;
-
default:
break;
}
diff --git a/ui/views/controls/tree/tree_view_win.h b/ui/views/controls/tree/tree_view_win.h
index 32318f1..c5fef22 100644
--- a/ui/views/controls/tree/tree_view_win.h
+++ b/ui/views/controls/tree/tree_view_win.h
@@ -29,6 +29,10 @@ class VIEWS_EXPORT TreeView : public NativeControl, ui::TreeModelObserver {
TreeView();
virtual ~TreeView();
+ // Returns this. Intended for implementations (such as TreeViewViews) that
+ // need to wrap the tree in a scrollview.
+ View* CreateParentIfNecessary();
+
// 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_; }
diff --git a/ui/views/examples/examples_window.cc b/ui/views/examples/examples_window.cc
index 9aad8ed..a60baa6 100644
--- a/ui/views/examples/examples_window.cc
+++ b/ui/views/examples/examples_window.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -31,6 +31,7 @@
#include "ui/views/examples/text_example.h"
#include "ui/views/examples/textfield_example.h"
#include "ui/views/examples/throbber_example.h"
+#include "ui/views/examples/tree_view_example.h"
#include "ui/views/examples/widget_example.h"
#include "ui/views/focus/accelerator_handler.h"
#include "ui/views/layout/grid_layout.h"
@@ -110,6 +111,7 @@ class ExamplesWindowContents : public views::WidgetDelegateView {
// Adds all the individual examples to the tab strip.
void AddExamples() {
+ AddExample(new TreeViewExample);
AddExample(new BubbleExample);
AddExample(new ButtonExample);
AddExample(new ComboboxExample);
diff --git a/ui/views/examples/tree_view_example.cc b/ui/views/examples/tree_view_example.cc
new file mode 100644
index 0000000..c7b7003d
--- /dev/null
+++ b/ui/views/examples/tree_view_example.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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 "ui/views/examples/tree_view_example.h"
+
+#include "base/utf_string_conversions.h"
+#include "ui/views/controls/tree/tree_view.h"
+#include "ui/views/controls/tree/tree_view.h"
+#include "ui/views/layout/grid_layout.h"
+
+namespace views {
+namespace examples {
+
+TreeViewExample::TreeViewExample()
+ : ExampleBase("Tree View"),
+ model_(new NodeType(ASCIIToUTF16("root"), 1)) {
+}
+
+TreeViewExample::~TreeViewExample() {
+}
+
+void TreeViewExample::CreateExampleView(View* container) {
+ // Add some sample data.
+ NodeType* colors_node = new NodeType(ASCIIToUTF16("colors"), 1);
+ model_.GetRoot()->Add(colors_node, 0);
+ colors_node->Add(new NodeType(ASCIIToUTF16("red"), 1), 0);
+ colors_node->Add(new NodeType(ASCIIToUTF16("green"), 1), 1);
+ colors_node->Add(new NodeType(ASCIIToUTF16("blue"), 1), 2);
+
+ NodeType* sheep_node = new NodeType(ASCIIToUTF16("sheep"), 1);
+ model_.GetRoot()->Add(sheep_node, 0);
+ sheep_node->Add(new NodeType(ASCIIToUTF16("Sheep 1"), 1), 0);
+ sheep_node->Add(new NodeType(ASCIIToUTF16("Sheep 2"), 1), 1);
+
+ tree_view_ = new TreeView();
+ tree_view_->SetRootShown(false);
+ tree_view_->set_lines_at_root(true);
+ tree_view_->SetModel(&model_);
+ tree_view_->SetController(this);
+ add_ = new TextButton(this, ASCIIToUTF16("Add"));
+ add_->set_focusable(true);
+ remove_ = new TextButton(this, ASCIIToUTF16("Remove"));
+ remove_->set_focusable(true);
+ change_title_ = new TextButton(this, ASCIIToUTF16("Change Title"));
+ change_title_->set_focusable(true);
+
+ GridLayout* layout = new GridLayout(container);
+ container->SetLayoutManager(layout);
+
+ const int tree_view_column = 0;
+ ColumnSet* column_set = layout->AddColumnSet(tree_view_column);
+ column_set->AddColumn(GridLayout::FILL, GridLayout::FILL,
+ 1.0f, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(1 /* expand */, tree_view_column);
+ layout->AddView(tree_view_->CreateParentIfNecessary());
+
+ // Add control buttons horizontally.
+ const int button_column = 1;
+ column_set = layout->AddColumnSet(button_column);
+ for (int i = 0; i < 3; i++) {
+ column_set->AddColumn(GridLayout::FILL, GridLayout::FILL,
+ 1.0f, GridLayout::USE_PREF, 0, 0);
+ }
+
+ layout->StartRow(0 /* no expand */, button_column);
+ layout->AddView(add_);
+ layout->AddView(remove_);
+ layout->AddView(change_title_);
+}
+
+void TreeViewExample::ButtonPressed(Button* sender, const Event& event) {
+ NodeType* selected_node =
+ static_cast<NodeType*>(tree_view_->GetSelectedNode());
+ if (sender == add_) {
+ if (!selected_node)
+ selected_node = model_.GetRoot();
+ NodeType* new_node = new NodeType(selected_node->GetTitle(), 1);
+ model_.Add(selected_node, new_node, selected_node->child_count());
+ tree_view_->SetSelectedNode(new_node);
+ } else if (sender == remove_) {
+ DCHECK(selected_node);
+ DCHECK_NE(model_.GetRoot(), selected_node);
+ model_.Remove(selected_node->parent(), selected_node);
+ } else if (sender == change_title_) {
+ DCHECK(selected_node);
+ model_.SetTitle(selected_node,
+ selected_node->GetTitle() + ASCIIToUTF16("new"));
+ }
+}
+
+void TreeViewExample::OnTreeViewSelectionChanged(TreeView* tree_view) {
+ ui::TreeModelNode* node = tree_view_->GetSelectedNode();
+ if (node) {
+ change_title_->SetEnabled(true);
+ remove_->SetEnabled(node != model_.GetRoot());
+ } else {
+ change_title_->SetEnabled(false);
+ remove_->SetEnabled(false);
+ }
+}
+
+bool TreeViewExample::CanEdit(TreeView* tree_view,
+ ui::TreeModelNode* node) {
+ return true;
+}
+
+} // namespace examples
+} // namespace views
diff --git a/ui/views/examples/tree_view_example.h b/ui/views/examples/tree_view_example.h
new file mode 100644
index 0000000..80d307d
--- /dev/null
+++ b/ui/views/examples/tree_view_example.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 UI_VIEWS_EXAMPLES_TREE_VIEW_EXAMPLE_H_
+#define UI_VIEWS_EXAMPLES_TREE_VIEW_EXAMPLE_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/models/tree_node_model.h"
+#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/tree/tree_view_controller.h"
+#include "ui/views/examples/example_base.h"
+
+namespace views {
+
+class TreeView;
+
+namespace examples {
+
+class TreeViewExample : public ExampleBase,
+ public ButtonListener,
+ public TreeViewController {
+ public:
+ TreeViewExample();
+ virtual ~TreeViewExample();
+
+ // ExampleBase:
+ virtual void CreateExampleView(View* container) OVERRIDE;
+
+ private:
+ // ButtonListener:
+ virtual void ButtonPressed(Button* sender, const Event& event) OVERRIDE;
+
+ // TreeViewController:
+ virtual void OnTreeViewSelectionChanged(TreeView* tree_view) OVERRIDE;
+ virtual bool CanEdit(TreeView* tree_view, ui::TreeModelNode* node) OVERRIDE;
+
+ // The tree view to be tested.
+ TreeView* tree_view_;
+
+ // Control buttons to modify the model.
+ Button* add_;
+ Button* remove_;
+ Button* change_title_;
+
+ typedef ui::TreeNodeWithValue<int> NodeType;
+
+ ui::TreeNodeModel<NodeType> model_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeViewExample);
+};
+
+} // namespace examples
+} // namespace views
+
+#endif // UI_VIEWS_EXAMPLES_TREE_VIEW_EXAMPLE_H_
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 84a3119..bab5410 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -238,6 +238,8 @@
'controls/tree/tree_view.h',
'controls/tree/tree_view_controller.cc',
'controls/tree/tree_view_controller.h',
+ 'controls/tree/tree_view_views.cc',
+ 'controls/tree/tree_view_views.h',
'controls/tree/tree_view_win.cc',
'controls/tree/tree_view_win.h',
#'debug_utils.cc',
@@ -405,9 +407,6 @@
'controls/table/table_view2.cc',
'controls/table/table_view2.h',
'controls/table/table_view_observer.h',
- 'controls/tree/tree_view.h',
- 'controls/tree/tree_view_controller.cc',
- 'controls/tree/tree_view_controller.h',
'drag_utils_win.cc',
'widget/aero_tooltip_manager.cc',
'widget/aero_tooltip_manager.h',
@@ -427,8 +426,6 @@
'controls/scrollbar/bitmap_scroll_bar.cc',
'controls/table/group_table_view.cc',
'controls/table/table_view.cc',
- 'controls/tree/tree_view_controller.cc',
- 'controls/tree/tree_view_controller.h',
'controls/tree/tree_view.h',
'events/event_win.cc',
'widget/aero_tooltip_manager.cc',
@@ -444,12 +441,13 @@
'widget/tooltip_manager_views.cc',
],
}],
- # For these we still use platform code instead of Views code on Windows.
['use_aura==0 and OS=="win"', {
'sources!': [
'controls/menu/menu_config_views.cc',
'controls/menu/menu_item_view_views.cc',
'controls/menu/menu_separator_views.cc',
+ 'controls/tree/tree_view_views.cc',
+ 'controls/tree/tree_view_views.h',
],
}],
['OS=="win"', {
@@ -510,6 +508,7 @@
'controls/table/table_view_unittest.cc',
'controls/textfield/native_textfield_views_unittest.cc',
'controls/textfield/textfield_views_model_unittest.cc',
+ 'controls/tree/tree_view_views_unittest.cc',
'events/event_unittest.cc',
'focus/accelerator_handler_gtk_unittest.cc',
'focus/focus_manager_test.h',
@@ -562,6 +561,11 @@
'../third_party/wtl/include',
],
}],
+ ['use_aura==0 and OS=="win"', {
+ 'sources/': [
+ ['exclude', 'controls/tree/tree_view_views_unittest.cc'],
+ ],
+ }],
[ 'use_aura==1', {
'dependencies': [
'../aura/aura.gyp:test_support_aura',
@@ -656,6 +660,8 @@
'examples/textfield_example.h',
'examples/throbber_example.cc',
'examples/throbber_example.h',
+ 'examples/tree_view_example.cc',
+ 'examples/tree_view_example.h',
'examples/widget_example.cc',
'examples/widget_example.h',
],