diff options
Diffstat (limited to 'ui/views/controls/tree/tree_view_views.cc')
-rw-r--r-- | ui/views/controls/tree/tree_view_views.cc | 779 |
1 files changed, 779 insertions, 0 deletions
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 |