diff options
-rw-r--r-- | chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/views/collected_cookies_win.cc | 9 | ||||
-rw-r--r-- | ui/views/controls/scroll_view.cc | 21 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view.h | 6 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_controller.cc | 3 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_controller.h | 3 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_views.cc | 779 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_views.h | 384 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_views_unittest.cc | 384 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_win.cc | 13 | ||||
-rw-r--r-- | ui/views/controls/tree/tree_view_win.h | 4 | ||||
-rw-r--r-- | ui/views/examples/examples_window.cc | 4 | ||||
-rw-r--r-- | ui/views/examples/tree_view_example.cc | 109 | ||||
-rw-r--r-- | ui/views/examples/tree_view_example.h | 58 | ||||
-rw-r--r-- | ui/views/views.gyp | 18 |
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(), ¤t_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(), ¤t_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', ], |