| // 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.h" |
| |
| #include <algorithm> |
| |
| #include "base/i18n/rtl.h" |
| #include "base/message_loop/message_loop.h" |
| #include "grit/ui_resources.h" |
| #include "ui/base/accessibility/accessible_view_state.h" |
| #include "ui/base/events/event.h" |
| #include "ui/base/keycodes/keyboard_codes.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/rect_conversions.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/controls/tree/tree_view_controller.h" |
| #include "ui/views/controls/tree/tree_view_selector.h" |
| #include "ui/views/ime/input_method.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; |
| |
| namespace { |
| |
| // Returns the color id for the background of selected text. |has_focus| |
| // indicates if the tree has focus. |
| ui::NativeTheme::ColorId text_background_color_id(bool has_focus) { |
| return has_focus ? |
| ui::NativeTheme::kColorId_TreeSelectionBackgroundFocused : |
| ui::NativeTheme::kColorId_TreeSelectionBackgroundUnfocused; |
| } |
| |
| // Returns the color id for text. |has_focus| indicates if the tree has focus |
| // and |is_selected| is true if the item is selected. |
| ui::NativeTheme::ColorId text_color_id(bool has_focus, bool is_selected) { |
| if (is_selected) { |
| if (has_focus) |
| return ui::NativeTheme::kColorId_TreeSelectedText; |
| return ui::NativeTheme::kColorId_TreeSelectedTextUnfocused; |
| } |
| return ui::NativeTheme::kColorId_TreeText; |
| } |
| |
| } // namespace |
| |
| TreeView::TreeView() |
| : model_(NULL), |
| selected_node_(NULL), |
| editing_(false), |
| editor_(NULL), |
| focus_manager_(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); |
| closed_icon_ = *ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| (base::i18n::IsRTL() ? IDR_FOLDER_CLOSED_RTL |
| : IDR_FOLDER_CLOSED)).ToImageSkia(); |
| open_icon_ = *ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| (base::i18n::IsRTL() ? IDR_FOLDER_OPEN_RTL |
| : IDR_FOLDER_OPEN)).ToImageSkia(); |
| text_offset_ = closed_icon_.width() + kImagePadding + kImagePadding + |
| kArrowRegionSize; |
| } |
| |
| TreeView::~TreeView() { |
| if (model_) |
| model_->RemoveObserver(this); |
| if (focus_manager_) { |
| focus_manager_->RemoveFocusChangeListener(this); |
| focus_manager_ = NULL; |
| } |
| } |
| |
| View* TreeView::CreateParentIfNecessary() { |
| ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder(); |
| 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(!editing_); |
| editing_ = true; |
| if (!editor_) { |
| editor_ = new Textfield; |
| // Add the editor immediately as GetPreferredSize returns the wrong thing if |
| // not parented. |
| AddChildView(editor_); |
| editor_->SetFont(font_); |
| empty_editor_size_ = editor_->GetPreferredSize(); |
| editor_->SetController(this); |
| } |
| editor_->SetText(selected_node_->model_node()->GetTitle()); |
| LayoutEditor(); |
| editor_->SetVisible(true); |
| SchedulePaintForNode(selected_node_); |
| editor_->RequestFocus(); |
| editor_->SelectAll(false); |
| |
| // Listen for focus changes so that we can cancel editing. |
| focus_manager_ = GetFocusManager(); |
| if (focus_manager_) |
| focus_manager_->AddFocusChangeListener(this); |
| |
| // Accelerators to commit/cancel edit. |
| AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); |
| AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); |
| } |
| |
| void TreeView::CancelEdit() { |
| if (!editing_) |
| return; |
| |
| // WARNING: don't touch |selected_node_|, it may be bogus. |
| |
| editing_ = false; |
| if (focus_manager_) { |
| focus_manager_->RemoveFocusChangeListener(this); |
| focus_manager_ = NULL; |
| } |
| editor_->SetVisible(false); |
| SchedulePaint(); |
| |
| RemoveAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); |
| RemoveAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); |
| } |
| |
| void TreeView::CommitEdit() { |
| if (!editing_) |
| return; |
| |
| DCHECK(selected_node_); |
| const bool editor_has_focus = editor_->HasFocus(); |
| model_->SetTitle(GetSelectedNode(), editor_->text()); |
| CancelEdit(); |
| if (editor_has_focus) |
| RequestFocus(); |
| } |
| |
| TreeModelNode* TreeView::GetEditingNode() { |
| return editing_ ? selected_node_->model_node() : NULL; |
| } |
| |
| void TreeView::SetSelectedNode(TreeModelNode* model_node) { |
| if (editing_ || model_node != selected_node_) |
| CancelEdit(); |
| 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 (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(); |
| } |
| |
| int TreeView::GetRowCount() { |
| int row_count = root_.NumExpandedNodes(); |
| if (!root_shown_) |
| row_count--; |
| return row_count; |
| } |
| |
| ui::TreeModelNode* TreeView::GetNodeForRow(int row) { |
| int depth = 0; |
| InternalNode* node = GetNodeByRow(row, &depth); |
| return node ? node->model_node() : NULL; |
| } |
| |
| int TreeView::GetRowForNode(ui::TreeModelNode* node) { |
| InternalNode* internal_node = |
| GetInternalNodeForModelNode(node, DONT_CREATE_IF_NOT_LOADED); |
| if (!internal_node) |
| return -1; |
| int depth = 0; |
| return GetRowForInternalNode(internal_node, &depth); |
| } |
| |
| 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); |
| LayoutEditor(); |
| } |
| |
| gfx::Size TreeView::GetPreferredSize() { |
| return preferred_size_; |
| } |
| |
| bool TreeView::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| if (accelerator.key_code() == ui::VKEY_RETURN) { |
| CommitEdit(); |
| } else { |
| DCHECK_EQ(ui::VKEY_ESCAPE, accelerator.key_code()); |
| CancelEdit(); |
| RequestFocus(); |
| } |
| return true; |
| } |
| |
| bool TreeView::OnMousePressed(const ui::MouseEvent& event) { |
| return OnClickOrTap(event); |
| } |
| |
| ui::TextInputClient* TreeView::GetTextInputClient() { |
| if (!selector_) |
| selector_.reset(new TreeViewSelector(this)); |
| return selector_.get(); |
| } |
| |
| void TreeView::OnGestureEvent(ui::GestureEvent* event) { |
| if (event->type() == ui::ET_GESTURE_TAP) { |
| if (OnClickOrTap(*event)) |
| event->SetHandled(); |
| } |
| } |
| |
| void TreeView::ShowContextMenu(const gfx::Point& p, |
| ui::MenuSourceType source_type) { |
| if (!model_) |
| return; |
| if (source_type == ui::MENU_SOURCE_MOUSE) { |
| // Only invoke View's implementation (which notifies the |
| // ContextMenuController) if over a node. |
| gfx::Point local_point(p); |
| ConvertPointToTarget(NULL, this, &local_point); |
| int row = (local_point.y() - kVerticalInset) / row_height_; |
| int depth = 0; |
| InternalNode* node = GetNodeByRow(row, &depth); |
| if (!node) |
| return; |
| gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth)); |
| if (!bounds.Contains(local_point)) |
| return; |
| } |
| View::ShowContextMenu(p, source_type); |
| } |
| |
| 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 (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 |
| // its 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(); |
| } |
| } |
| |
| void TreeView::ContentsChanged(Textfield* sender, |
| const string16& new_contents) { |
| } |
| |
| bool TreeView::HandleKeyEvent(Textfield* sender, |
| const ui::KeyEvent& key_event) { |
| switch (key_event.key_code()) { |
| case ui::VKEY_RETURN: |
| CommitEdit(); |
| return true; |
| |
| case ui::VKEY_ESCAPE: |
| CancelEdit(); |
| RequestFocus(); |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| void TreeView::OnWillChangeFocus(View* focused_before, View* focused_now) { |
| } |
| |
| void TreeView::OnDidChangeFocus(View* focused_before, View* focused_now) { |
| CommitEdit(); |
| } |
| |
| 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 ui::KeyEvent& event) { |
| if (!HasFocus()) |
| return false; |
| |
| switch (event.key_code()) { |
| case ui::VKEY_F2: |
| if (!editing_) { |
| 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. |
| canvas->DrawColor(GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_TreeBackground)); |
| |
| int min_y, max_y; |
| { |
| SkRect sk_clip_rect; |
| if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect)) { |
| // Pixels partially inside the clip rect should be included. |
| gfx::Rect clip_rect = gfx::ToEnclosingRect( |
| gfx::SkRectToRectF(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() { |
| View::OnFocus(); |
| SchedulePaintForNode(selected_node_); |
| |
| // Notify the InputMethod so that it knows to query the TextInputClient. |
| if (GetInputMethod()) |
| GetInputMethod()->OnCaretBoundsChanged(this); |
| } |
| |
| void TreeView::OnBlur() { |
| SchedulePaintForNode(selected_node_); |
| if (selector_) |
| selector_->OnTreeViewBlur(); |
| } |
| |
| bool TreeView::OnClickOrTap(const ui::LocatedEvent& event) { |
| CommitEdit(); |
| RequestFocus(); |
| |
| int row = (event.y() - kVerticalInset) / row_height_; |
| int depth = 0; |
| InternalNode* node = GetNodeByRow(row, &depth); |
| if (node) { |
| gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth)); |
| if (bounds.Contains(event.location())) { |
| int relative_x = event.x() - bounds.x(); |
| if (base::i18n::IsRTL()) |
| relative_x = bounds.width() - relative_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()); |
| bool should_toggle = false; |
| if (event.type() == ui::ET_GESTURE_TAP) { |
| const ui::GestureEvent& gesture = |
| static_cast<const ui::GestureEvent&>(event); |
| should_toggle = gesture.details().tap_count() == 2; |
| } else { |
| should_toggle = (event.flags() & ui::EF_IS_DOUBLE_CLICK) != 0; |
| } |
| if (should_toggle) { |
| if (node->is_expanded()) |
| Collapse(node->model_node()); |
| else |
| Expand(node->model_node()); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| 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::Canvas::SizeStringInt(node->model_node()->GetTitle(), font_, |
| &width, &height, 0, 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::LayoutEditor() { |
| if (!editing_) |
| return; |
| |
| DCHECK(selected_node_); |
| // Position the editor so that its text aligns with the text we drew. |
| gfx::Rect row_bounds = GetBoundsForNode(selected_node_); |
| row_bounds.set_x( |
| GetMirroredXWithWidthInView(row_bounds.x(), row_bounds.width())); |
| row_bounds.set_x(row_bounds.x() + text_offset_); |
| row_bounds.set_width(row_bounds.width() - text_offset_); |
| row_bounds.Inset(kTextHorizontalPadding, kTextVerticalPadding); |
| row_bounds.Inset(-empty_editor_size_.width() / 2, |
| -(empty_editor_size_.height() - font_.GetHeight()) / 2); |
| // Give a little extra space for editing. |
| row_bounds.set_width(row_bounds.width() + 50); |
| editor_->SetBoundsRect(row_bounds); |
| editor_->Layout(); |
| } |
| |
| 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. |
| gfx::ImageSkia 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_; |
| int icon_x = kArrowRegionSize + kImagePadding + |
| (open_icon_.width() - icon.width()) / 2; |
| if (base::i18n::IsRTL()) |
| icon_x = bounds.right() - icon_x - open_icon_.width(); |
| else |
| icon_x += bounds.x(); |
| canvas->DrawImageInt( |
| icon, icon_x, |
| bounds.y() + (bounds.height() - icon.height()) / 2); |
| |
| if (!editing_ || node != selected_node_) { |
| gfx::Rect text_bounds(bounds.x() + text_offset_, bounds.y(), |
| bounds.width() - text_offset_, bounds.height()); |
| if (base::i18n::IsRTL()) |
| text_bounds.set_x(bounds.x()); |
| if (node == selected_node_) { |
| const SkColor bg_color = GetNativeTheme()->GetSystemColor( |
| text_background_color_id(HasFocus())); |
| canvas->FillRect(text_bounds, bg_color); |
| if (HasFocus()) |
| canvas->DrawFocusRect(text_bounds); |
| } |
| const ui::NativeTheme::ColorId color_id = |
| text_color_id(HasFocus(), node == selected_node_); |
| canvas->DrawStringInt(node->model_node()->GetTitle(), font_, |
| GetNativeTheme()->GetSystemColor(color_id), |
| 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; |
| if (base::i18n::IsRTL()) { |
| center_x = node_bounds.right() - kArrowRegionSize + |
| (kArrowRegionSize - 4) / 2; |
| } else { |
| center_x = node_bounds.x() + (kArrowRegionSize - 4) / 2; |
| } |
| int center_y = node_bounds.y() + node_bounds.height() / 2; |
| const SkColor arrow_color = GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_TreeArrow); |
| // TODO: this should come from an image. |
| if (!expanded) { |
| int delta = base::i18n::IsRTL() ? 1 : -1; |
| for (int i = 0; i < 4; ++i) { |
| canvas->FillRect(gfx::Rect(center_x + delta * (2 - i), |
| center_y - (3 - i), 1, (3 - i) * 2 + 1), |
| arrow_color); |
| } |
| } else { |
| center_y -= 2; |
| for (int i = 0; i < 4; ++i) { |
| canvas->FillRect(gfx::Rect(center_x - (3 - i), center_y + i, |
| (3 - i) * 2 + 1, 1), arrow_color); |
| } |
| } |
| } |
| |
| 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 = GetRowForInternalNode(node, &depth); |
| return GetBoundsForNodeImpl(node, row, depth); |
| } |
| |
| gfx::Rect TreeView::GetBoundsForNodeImpl(InternalNode* node, |
| int row, |
| int depth) { |
| gfx::Rect rect(depth * kIndent + kHorizontalInset, |
| row * row_height_ + kVerticalInset, |
| text_offset_ + node->text_width() + |
| kTextHorizontalPadding * 2, |
| row_height_); |
| rect.set_x(GetMirroredXWithWidthInView(rect.x(), rect.width())); |
| return rect; |
| } |
| |
| int TreeView::GetRowForInternalNode(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 = 0; |
| 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 depth = 0; |
| int delta = type == INCREMENT_PREVIOUS ? -1 : 1; |
| int row = GetRowForInternalNode(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 |