| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ui/views/controls/tree/tree_view.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/i18n/rtl.h" | |
| 10 #include "base/message_loop/message_loop.h" | |
| 11 #include "ui/accessibility/ax_view_state.h" | |
| 12 #include "ui/base/resource/resource_bundle.h" | |
| 13 #include "ui/events/event.h" | |
| 14 #include "ui/events/keycodes/keyboard_codes.h" | |
| 15 #include "ui/gfx/canvas.h" | |
| 16 #include "ui/gfx/geometry/rect.h" | |
| 17 #include "ui/gfx/image/image.h" | |
| 18 #include "ui/gfx/rect_conversions.h" | |
| 19 #include "ui/gfx/skia_util.h" | |
| 20 #include "ui/native_theme/native_theme.h" | |
| 21 #include "ui/resources/grit/ui_resources.h" | |
| 22 #include "ui/views/controls/prefix_selector.h" | |
| 23 #include "ui/views/controls/scroll_view.h" | |
| 24 #include "ui/views/controls/textfield/textfield.h" | |
| 25 #include "ui/views/controls/tree/tree_view_controller.h" | |
| 26 #include "ui/views/ime/input_method.h" | |
| 27 | |
| 28 using ui::TreeModel; | |
| 29 using ui::TreeModelNode; | |
| 30 | |
| 31 namespace views { | |
| 32 | |
| 33 // Insets around the view. | |
| 34 static const int kHorizontalInset = 2; | |
| 35 static const int kVerticalInset = 2; | |
| 36 // Padding before/after the image. | |
| 37 static const int kImagePadding = 4; | |
| 38 // Size of the arrow region. | |
| 39 static const int kArrowRegionSize = 12; | |
| 40 // Padding around the text (on each side). | |
| 41 static const int kTextVerticalPadding = 3; | |
| 42 static const int kTextHorizontalPadding = 2; | |
| 43 // How much children are indented from their parent. | |
| 44 static const int kIndent = 20; | |
| 45 | |
| 46 // static | |
| 47 const char TreeView::kViewClassName[] = "TreeView"; | |
| 48 | |
| 49 namespace { | |
| 50 | |
| 51 // Returns the color id for the background of selected text. |has_focus| | |
| 52 // indicates if the tree has focus. | |
| 53 ui::NativeTheme::ColorId text_background_color_id(bool has_focus) { | |
| 54 return has_focus ? | |
| 55 ui::NativeTheme::kColorId_TreeSelectionBackgroundFocused : | |
| 56 ui::NativeTheme::kColorId_TreeSelectionBackgroundUnfocused; | |
| 57 } | |
| 58 | |
| 59 // Returns the color id for text. |has_focus| indicates if the tree has focus | |
| 60 // and |is_selected| is true if the item is selected. | |
| 61 ui::NativeTheme::ColorId text_color_id(bool has_focus, bool is_selected) { | |
| 62 if (is_selected) { | |
| 63 if (has_focus) | |
| 64 return ui::NativeTheme::kColorId_TreeSelectedText; | |
| 65 return ui::NativeTheme::kColorId_TreeSelectedTextUnfocused; | |
| 66 } | |
| 67 return ui::NativeTheme::kColorId_TreeText; | |
| 68 } | |
| 69 | |
| 70 } // namespace | |
| 71 | |
| 72 TreeView::TreeView() | |
| 73 : model_(NULL), | |
| 74 selected_node_(NULL), | |
| 75 editing_(false), | |
| 76 editor_(NULL), | |
| 77 focus_manager_(NULL), | |
| 78 auto_expand_children_(false), | |
| 79 editable_(true), | |
| 80 controller_(NULL), | |
| 81 root_shown_(true), | |
| 82 row_height_(font_list_.GetHeight() + kTextVerticalPadding * 2) { | |
| 83 SetFocusable(true); | |
| 84 closed_icon_ = *ui::ResourceBundle::GetSharedInstance().GetImageNamed( | |
| 85 (base::i18n::IsRTL() ? IDR_FOLDER_CLOSED_RTL | |
| 86 : IDR_FOLDER_CLOSED)).ToImageSkia(); | |
| 87 open_icon_ = *ui::ResourceBundle::GetSharedInstance().GetImageNamed( | |
| 88 (base::i18n::IsRTL() ? IDR_FOLDER_OPEN_RTL | |
| 89 : IDR_FOLDER_OPEN)).ToImageSkia(); | |
| 90 text_offset_ = closed_icon_.width() + kImagePadding + kImagePadding + | |
| 91 kArrowRegionSize; | |
| 92 } | |
| 93 | |
| 94 TreeView::~TreeView() { | |
| 95 if (model_) | |
| 96 model_->RemoveObserver(this); | |
| 97 if (focus_manager_) { | |
| 98 focus_manager_->RemoveFocusChangeListener(this); | |
| 99 focus_manager_ = NULL; | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 View* TreeView::CreateParentIfNecessary() { | |
| 104 ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder(); | |
| 105 scroll_view->SetContents(this); | |
| 106 return scroll_view; | |
| 107 } | |
| 108 | |
| 109 void TreeView::SetModel(TreeModel* model) { | |
| 110 if (model == model_) | |
| 111 return; | |
| 112 if (model_) | |
| 113 model_->RemoveObserver(this); | |
| 114 | |
| 115 CancelEdit(); | |
| 116 | |
| 117 model_ = model; | |
| 118 selected_node_ = NULL; | |
| 119 icons_.clear(); | |
| 120 if (model_) { | |
| 121 model_->AddObserver(this); | |
| 122 model_->GetIcons(&icons_); | |
| 123 | |
| 124 root_.RemoveAll(); | |
| 125 ConfigureInternalNode(model_->GetRoot(), &root_); | |
| 126 LoadChildren(&root_); | |
| 127 root_.set_is_expanded(true); | |
| 128 if (root_shown_) | |
| 129 selected_node_ = &root_; | |
| 130 else if (root_.child_count()) | |
| 131 selected_node_ = root_.GetChild(0); | |
| 132 } | |
| 133 DrawnNodesChanged(); | |
| 134 } | |
| 135 | |
| 136 void TreeView::SetEditable(bool editable) { | |
| 137 if (editable == editable_) | |
| 138 return; | |
| 139 editable_ = editable; | |
| 140 CancelEdit(); | |
| 141 } | |
| 142 | |
| 143 void TreeView::StartEditing(TreeModelNode* node) { | |
| 144 DCHECK(node); | |
| 145 // Cancel the current edit. | |
| 146 CancelEdit(); | |
| 147 // Make sure all ancestors are expanded. | |
| 148 if (model_->GetParent(node)) | |
| 149 Expand(model_->GetParent(node)); | |
| 150 // Select the node, else if the user commits the edit the selection reverts. | |
| 151 SetSelectedNode(node); | |
| 152 if (GetSelectedNode() != node) | |
| 153 return; // Selection failed for some reason, don't start editing. | |
| 154 DCHECK(!editing_); | |
| 155 editing_ = true; | |
| 156 if (!editor_) { | |
| 157 editor_ = new Textfield; | |
| 158 // Add the editor immediately as GetPreferredSize returns the wrong thing if | |
| 159 // not parented. | |
| 160 AddChildView(editor_); | |
| 161 editor_->SetFontList(font_list_); | |
| 162 empty_editor_size_ = editor_->GetPreferredSize(); | |
| 163 editor_->set_controller(this); | |
| 164 } | |
| 165 editor_->SetText(selected_node_->model_node()->GetTitle()); | |
| 166 LayoutEditor(); | |
| 167 editor_->SetVisible(true); | |
| 168 SchedulePaintForNode(selected_node_); | |
| 169 editor_->RequestFocus(); | |
| 170 editor_->SelectAll(false); | |
| 171 | |
| 172 // Listen for focus changes so that we can cancel editing. | |
| 173 focus_manager_ = GetFocusManager(); | |
| 174 if (focus_manager_) | |
| 175 focus_manager_->AddFocusChangeListener(this); | |
| 176 | |
| 177 // Accelerators to commit/cancel edit. | |
| 178 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); | |
| 179 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); | |
| 180 } | |
| 181 | |
| 182 void TreeView::CancelEdit() { | |
| 183 if (!editing_) | |
| 184 return; | |
| 185 | |
| 186 // WARNING: don't touch |selected_node_|, it may be bogus. | |
| 187 | |
| 188 editing_ = false; | |
| 189 if (focus_manager_) { | |
| 190 focus_manager_->RemoveFocusChangeListener(this); | |
| 191 focus_manager_ = NULL; | |
| 192 } | |
| 193 editor_->SetVisible(false); | |
| 194 SchedulePaint(); | |
| 195 | |
| 196 RemoveAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); | |
| 197 RemoveAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); | |
| 198 } | |
| 199 | |
| 200 void TreeView::CommitEdit() { | |
| 201 if (!editing_) | |
| 202 return; | |
| 203 | |
| 204 DCHECK(selected_node_); | |
| 205 const bool editor_has_focus = editor_->HasFocus(); | |
| 206 model_->SetTitle(GetSelectedNode(), editor_->text()); | |
| 207 CancelEdit(); | |
| 208 if (editor_has_focus) | |
| 209 RequestFocus(); | |
| 210 } | |
| 211 | |
| 212 TreeModelNode* TreeView::GetEditingNode() { | |
| 213 return editing_ ? selected_node_->model_node() : NULL; | |
| 214 } | |
| 215 | |
| 216 void TreeView::SetSelectedNode(TreeModelNode* model_node) { | |
| 217 if (editing_ || model_node != selected_node_) | |
| 218 CancelEdit(); | |
| 219 if (model_node && model_->GetParent(model_node)) | |
| 220 Expand(model_->GetParent(model_node)); | |
| 221 if (model_node && model_node == root_.model_node() && !root_shown_) | |
| 222 return; // Ignore requests to select the root when not shown. | |
| 223 InternalNode* node = model_node ? GetInternalNodeForModelNode( | |
| 224 model_node, CREATE_IF_NOT_LOADED) : NULL; | |
| 225 bool was_empty_selection = (selected_node_ == NULL); | |
| 226 bool changed = (selected_node_ != node); | |
| 227 if (changed) { | |
| 228 SchedulePaintForNode(selected_node_); | |
| 229 selected_node_ = node; | |
| 230 if (selected_node_ == &root_ && !root_shown_) | |
| 231 selected_node_ = NULL; | |
| 232 if (selected_node_ && selected_node_ != &root_) | |
| 233 Expand(model_->GetParent(selected_node_->model_node())); | |
| 234 SchedulePaintForNode(selected_node_); | |
| 235 } | |
| 236 | |
| 237 if (selected_node_) | |
| 238 ScrollRectToVisible(GetBoundsForNode(selected_node_)); | |
| 239 | |
| 240 // Notify controller if the old selection was empty to handle the case of | |
| 241 // remove explicitly resetting selected_node_ before invoking this. | |
| 242 if (controller_ && (changed || was_empty_selection)) | |
| 243 controller_->OnTreeViewSelectionChanged(this); | |
| 244 | |
| 245 if (changed) { | |
| 246 // TODO(dmazzoni): Decide if EVENT_SELECTION_CHANGED is a better choice for | |
| 247 // sub-item selection event. | |
| 248 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true); | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 TreeModelNode* TreeView::GetSelectedNode() { | |
| 253 return selected_node_ ? selected_node_->model_node() : NULL; | |
| 254 } | |
| 255 | |
| 256 void TreeView::Collapse(ui::TreeModelNode* model_node) { | |
| 257 // Don't collapse the root if the root isn't shown, otherwise nothing is | |
| 258 // displayed. | |
| 259 if (model_node == root_.model_node() && !root_shown_) | |
| 260 return; | |
| 261 InternalNode* node = | |
| 262 GetInternalNodeForModelNode(model_node, DONT_CREATE_IF_NOT_LOADED); | |
| 263 if (!node) | |
| 264 return; | |
| 265 bool was_expanded = IsExpanded(model_node); | |
| 266 if (node->is_expanded()) { | |
| 267 if (selected_node_ && selected_node_->HasAncestor(node)) | |
| 268 SetSelectedNode(model_node); | |
| 269 node->set_is_expanded(false); | |
| 270 } | |
| 271 if (was_expanded) | |
| 272 DrawnNodesChanged(); | |
| 273 } | |
| 274 | |
| 275 void TreeView::Expand(TreeModelNode* node) { | |
| 276 if (ExpandImpl(node)) | |
| 277 DrawnNodesChanged(); | |
| 278 // TODO: need to support auto_expand_children_. | |
| 279 } | |
| 280 | |
| 281 void TreeView::ExpandAll(TreeModelNode* node) { | |
| 282 DCHECK(node); | |
| 283 // Expand the node. | |
| 284 bool expanded_at_least_one = ExpandImpl(node); | |
| 285 // And recursively expand all the children. | |
| 286 for (int i = model_->GetChildCount(node) - 1; i >= 0; --i) { | |
| 287 TreeModelNode* child = model_->GetChild(node, i); | |
| 288 if (ExpandImpl(child)) | |
| 289 expanded_at_least_one = true; | |
| 290 } | |
| 291 if (expanded_at_least_one) | |
| 292 DrawnNodesChanged(); | |
| 293 } | |
| 294 | |
| 295 bool TreeView::IsExpanded(TreeModelNode* model_node) { | |
| 296 if (!model_node) { | |
| 297 // NULL check primarily for convenience for uses in this class so don't have | |
| 298 // to add NULL checks every where we look up the parent. | |
| 299 return true; | |
| 300 } | |
| 301 InternalNode* node = GetInternalNodeForModelNode( | |
| 302 model_node, DONT_CREATE_IF_NOT_LOADED); | |
| 303 if (!node) | |
| 304 return false; | |
| 305 | |
| 306 while (node) { | |
| 307 if (!node->is_expanded()) | |
| 308 return false; | |
| 309 node = node->parent(); | |
| 310 } | |
| 311 return true; | |
| 312 } | |
| 313 | |
| 314 void TreeView::SetRootShown(bool root_shown) { | |
| 315 if (root_shown_ == root_shown) | |
| 316 return; | |
| 317 root_shown_ = root_shown; | |
| 318 if (!root_shown_ && selected_node_ == &root_) { | |
| 319 if (model_->GetChildCount(root_.model_node())) | |
| 320 SetSelectedNode(model_->GetChild(root_.model_node(), 0)); | |
| 321 else | |
| 322 SetSelectedNode(NULL); | |
| 323 } | |
| 324 DrawnNodesChanged(); | |
| 325 } | |
| 326 | |
| 327 ui::TreeModelNode* TreeView::GetNodeForRow(int row) { | |
| 328 int depth = 0; | |
| 329 InternalNode* node = GetNodeByRow(row, &depth); | |
| 330 return node ? node->model_node() : NULL; | |
| 331 } | |
| 332 | |
| 333 int TreeView::GetRowForNode(ui::TreeModelNode* node) { | |
| 334 InternalNode* internal_node = | |
| 335 GetInternalNodeForModelNode(node, DONT_CREATE_IF_NOT_LOADED); | |
| 336 if (!internal_node) | |
| 337 return -1; | |
| 338 int depth = 0; | |
| 339 return GetRowForInternalNode(internal_node, &depth); | |
| 340 } | |
| 341 | |
| 342 void TreeView::Layout() { | |
| 343 int width = preferred_size_.width(); | |
| 344 int height = preferred_size_.height(); | |
| 345 if (parent()) { | |
| 346 width = std::max(parent()->width(), width); | |
| 347 height = std::max(parent()->height(), height); | |
| 348 } | |
| 349 SetBounds(x(), y(), width, height); | |
| 350 LayoutEditor(); | |
| 351 } | |
| 352 | |
| 353 gfx::Size TreeView::GetPreferredSize() const { | |
| 354 return preferred_size_; | |
| 355 } | |
| 356 | |
| 357 bool TreeView::AcceleratorPressed(const ui::Accelerator& accelerator) { | |
| 358 if (accelerator.key_code() == ui::VKEY_RETURN) { | |
| 359 CommitEdit(); | |
| 360 } else { | |
| 361 DCHECK_EQ(ui::VKEY_ESCAPE, accelerator.key_code()); | |
| 362 CancelEdit(); | |
| 363 RequestFocus(); | |
| 364 } | |
| 365 return true; | |
| 366 } | |
| 367 | |
| 368 bool TreeView::OnMousePressed(const ui::MouseEvent& event) { | |
| 369 return OnClickOrTap(event); | |
| 370 } | |
| 371 | |
| 372 ui::TextInputClient* TreeView::GetTextInputClient() { | |
| 373 if (!selector_) | |
| 374 selector_.reset(new PrefixSelector(this)); | |
| 375 return selector_.get(); | |
| 376 } | |
| 377 | |
| 378 void TreeView::OnGestureEvent(ui::GestureEvent* event) { | |
| 379 if (event->type() == ui::ET_GESTURE_TAP) { | |
| 380 if (OnClickOrTap(*event)) | |
| 381 event->SetHandled(); | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 void TreeView::ShowContextMenu(const gfx::Point& p, | |
| 386 ui::MenuSourceType source_type) { | |
| 387 if (!model_) | |
| 388 return; | |
| 389 if (source_type == ui::MENU_SOURCE_MOUSE) { | |
| 390 // Only invoke View's implementation (which notifies the | |
| 391 // ContextMenuController) if over a node. | |
| 392 gfx::Point local_point(p); | |
| 393 ConvertPointFromScreen(this, &local_point); | |
| 394 int row = (local_point.y() - kVerticalInset) / row_height_; | |
| 395 int depth = 0; | |
| 396 InternalNode* node = GetNodeByRow(row, &depth); | |
| 397 if (!node) | |
| 398 return; | |
| 399 gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth)); | |
| 400 if (!bounds.Contains(local_point)) | |
| 401 return; | |
| 402 } | |
| 403 View::ShowContextMenu(p, source_type); | |
| 404 } | |
| 405 | |
| 406 void TreeView::GetAccessibleState(ui::AXViewState* state) { | |
| 407 state->role = ui::AX_ROLE_TREE; | |
| 408 state->AddStateFlag(ui::AX_STATE_READ_ONLY); | |
| 409 if (!selected_node_) | |
| 410 return; | |
| 411 | |
| 412 // Get selected item info. | |
| 413 state->role = ui::AX_ROLE_TREE_ITEM; | |
| 414 state->name = selected_node_->model_node()->GetTitle(); | |
| 415 } | |
| 416 | |
| 417 const char* TreeView::GetClassName() const { | |
| 418 return kViewClassName; | |
| 419 } | |
| 420 | |
| 421 void TreeView::TreeNodesAdded(TreeModel* model, | |
| 422 TreeModelNode* parent, | |
| 423 int start, | |
| 424 int count) { | |
| 425 InternalNode* parent_node = | |
| 426 GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED); | |
| 427 if (!parent_node || !parent_node->loaded_children()) | |
| 428 return; | |
| 429 for (int i = 0; i < count; ++i) { | |
| 430 InternalNode* child = new InternalNode; | |
| 431 ConfigureInternalNode(model_->GetChild(parent, start + i), child); | |
| 432 parent_node->Add(child, start + i); | |
| 433 } | |
| 434 if (IsExpanded(parent)) | |
| 435 DrawnNodesChanged(); | |
| 436 } | |
| 437 | |
| 438 void TreeView::TreeNodesRemoved(TreeModel* model, | |
| 439 TreeModelNode* parent, | |
| 440 int start, | |
| 441 int count) { | |
| 442 InternalNode* parent_node = | |
| 443 GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED); | |
| 444 if (!parent_node || !parent_node->loaded_children()) | |
| 445 return; | |
| 446 bool reset_selection = false; | |
| 447 for (int i = 0; i < count; ++i) { | |
| 448 InternalNode* child_removing = parent_node->GetChild(start); | |
| 449 if (selected_node_ && selected_node_->HasAncestor(child_removing)) | |
| 450 reset_selection = true; | |
| 451 delete parent_node->Remove(child_removing); | |
| 452 } | |
| 453 if (reset_selection) { | |
| 454 // selected_node_ is no longer valid (at the time we enter this function | |
| 455 // its model_node() is likely deleted). Explicitly NULL out the field | |
| 456 // rather than invoking SetSelectedNode() otherwise, we'll try and use a | |
| 457 // deleted value. | |
| 458 selected_node_ = NULL; | |
| 459 TreeModelNode* to_select = parent; | |
| 460 if (parent == root_.model_node() && !root_shown_) { | |
| 461 to_select = model_->GetChildCount(parent) > 0 ? | |
| 462 model_->GetChild(parent, 0) : NULL; | |
| 463 } | |
| 464 SetSelectedNode(to_select); | |
| 465 } | |
| 466 if (IsExpanded(parent)) | |
| 467 DrawnNodesChanged(); | |
| 468 } | |
| 469 | |
| 470 void TreeView::TreeNodeChanged(TreeModel* model, TreeModelNode* model_node) { | |
| 471 InternalNode* node = | |
| 472 GetInternalNodeForModelNode(model_node, DONT_CREATE_IF_NOT_LOADED); | |
| 473 if (!node) | |
| 474 return; | |
| 475 int old_width = node->text_width(); | |
| 476 UpdateNodeTextWidth(node); | |
| 477 if (old_width != node->text_width() && | |
| 478 ((node == &root_ && root_shown_) || | |
| 479 (node != &root_ && IsExpanded(node->parent()->model_node())))) { | |
| 480 DrawnNodesChanged(); | |
| 481 } | |
| 482 } | |
| 483 | |
| 484 void TreeView::ContentsChanged(Textfield* sender, | |
| 485 const base::string16& new_contents) { | |
| 486 } | |
| 487 | |
| 488 bool TreeView::HandleKeyEvent(Textfield* sender, | |
| 489 const ui::KeyEvent& key_event) { | |
| 490 switch (key_event.key_code()) { | |
| 491 case ui::VKEY_RETURN: | |
| 492 CommitEdit(); | |
| 493 return true; | |
| 494 | |
| 495 case ui::VKEY_ESCAPE: | |
| 496 CancelEdit(); | |
| 497 RequestFocus(); | |
| 498 return true; | |
| 499 | |
| 500 default: | |
| 501 return false; | |
| 502 } | |
| 503 } | |
| 504 | |
| 505 void TreeView::OnWillChangeFocus(View* focused_before, View* focused_now) { | |
| 506 } | |
| 507 | |
| 508 void TreeView::OnDidChangeFocus(View* focused_before, View* focused_now) { | |
| 509 CommitEdit(); | |
| 510 } | |
| 511 | |
| 512 int TreeView::GetRowCount() { | |
| 513 int row_count = root_.NumExpandedNodes(); | |
| 514 if (!root_shown_) | |
| 515 row_count--; | |
| 516 return row_count; | |
| 517 } | |
| 518 | |
| 519 int TreeView::GetSelectedRow() { | |
| 520 ui::TreeModelNode* model_node = GetSelectedNode(); | |
| 521 return model_node ? GetRowForNode(model_node) : -1; | |
| 522 } | |
| 523 | |
| 524 void TreeView::SetSelectedRow(int row) { | |
| 525 SetSelectedNode(GetNodeForRow(row)); | |
| 526 } | |
| 527 | |
| 528 base::string16 TreeView::GetTextForRow(int row) { | |
| 529 return GetNodeForRow(row)->GetTitle(); | |
| 530 } | |
| 531 | |
| 532 gfx::Point TreeView::GetKeyboardContextMenuLocation() { | |
| 533 int y = height() / 2; | |
| 534 if (selected_node_) { | |
| 535 gfx::Rect node_bounds(GetBoundsForNode(selected_node_)); | |
| 536 gfx::Rect vis_bounds(GetVisibleBounds()); | |
| 537 if (node_bounds.y() >= vis_bounds.y() && | |
| 538 node_bounds.y() < vis_bounds.bottom()) { | |
| 539 y = node_bounds.y(); | |
| 540 } | |
| 541 } | |
| 542 gfx::Point screen_loc(0, y); | |
| 543 if (base::i18n::IsRTL()) | |
| 544 screen_loc.set_x(width()); | |
| 545 ConvertPointToScreen(this, &screen_loc); | |
| 546 return screen_loc; | |
| 547 } | |
| 548 | |
| 549 bool TreeView::OnKeyPressed(const ui::KeyEvent& event) { | |
| 550 if (!HasFocus()) | |
| 551 return false; | |
| 552 | |
| 553 switch (event.key_code()) { | |
| 554 case ui::VKEY_F2: | |
| 555 if (!editing_) { | |
| 556 TreeModelNode* selected_node = GetSelectedNode(); | |
| 557 if (selected_node && (!controller_ || | |
| 558 controller_->CanEdit(this, selected_node))) { | |
| 559 StartEditing(selected_node); | |
| 560 } | |
| 561 } | |
| 562 return true; | |
| 563 | |
| 564 case ui::VKEY_UP: | |
| 565 case ui::VKEY_DOWN: | |
| 566 IncrementSelection(event.key_code() == ui::VKEY_UP ? | |
| 567 INCREMENT_PREVIOUS : INCREMENT_NEXT); | |
| 568 return true; | |
| 569 | |
| 570 case ui::VKEY_LEFT: | |
| 571 if (base::i18n::IsRTL()) | |
| 572 ExpandOrSelectChild(); | |
| 573 else | |
| 574 CollapseOrSelectParent(); | |
| 575 return true; | |
| 576 | |
| 577 case ui::VKEY_RIGHT: | |
| 578 if (base::i18n::IsRTL()) | |
| 579 CollapseOrSelectParent(); | |
| 580 else | |
| 581 ExpandOrSelectChild(); | |
| 582 return true; | |
| 583 | |
| 584 default: | |
| 585 break; | |
| 586 } | |
| 587 return false; | |
| 588 } | |
| 589 | |
| 590 void TreeView::OnPaint(gfx::Canvas* canvas) { | |
| 591 // Don't invoke View::OnPaint so that we can render our own focus border. | |
| 592 canvas->DrawColor(GetNativeTheme()->GetSystemColor( | |
| 593 ui::NativeTheme::kColorId_TreeBackground)); | |
| 594 | |
| 595 int min_y, max_y; | |
| 596 { | |
| 597 SkRect sk_clip_rect; | |
| 598 if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect)) { | |
| 599 // Pixels partially inside the clip rect should be included. | |
| 600 gfx::Rect clip_rect = gfx::ToEnclosingRect( | |
| 601 gfx::SkRectToRectF(sk_clip_rect)); | |
| 602 min_y = clip_rect.y(); | |
| 603 max_y = clip_rect.bottom(); | |
| 604 } else { | |
| 605 gfx::Rect vis_bounds = GetVisibleBounds(); | |
| 606 min_y = vis_bounds.y(); | |
| 607 max_y = vis_bounds.bottom(); | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 int min_row = std::max(0, (min_y - kVerticalInset) / row_height_); | |
| 612 int max_row = (max_y - kVerticalInset) / row_height_; | |
| 613 if ((max_y - kVerticalInset) % row_height_ != 0) | |
| 614 max_row++; | |
| 615 int current_row = root_row(); | |
| 616 PaintRows(canvas, min_row, max_row, &root_, root_depth(), ¤t_row); | |
| 617 } | |
| 618 | |
| 619 void TreeView::OnFocus() { | |
| 620 GetInputMethod()->OnFocus(); | |
| 621 View::OnFocus(); | |
| 622 SchedulePaintForNode(selected_node_); | |
| 623 | |
| 624 // Notify the InputMethod so that it knows to query the TextInputClient. | |
| 625 if (GetInputMethod()) | |
| 626 GetInputMethod()->OnCaretBoundsChanged(this); | |
| 627 } | |
| 628 | |
| 629 void TreeView::OnBlur() { | |
| 630 GetInputMethod()->OnBlur(); | |
| 631 SchedulePaintForNode(selected_node_); | |
| 632 if (selector_) | |
| 633 selector_->OnViewBlur(); | |
| 634 } | |
| 635 | |
| 636 bool TreeView::OnClickOrTap(const ui::LocatedEvent& event) { | |
| 637 CommitEdit(); | |
| 638 RequestFocus(); | |
| 639 | |
| 640 int row = (event.y() - kVerticalInset) / row_height_; | |
| 641 int depth = 0; | |
| 642 InternalNode* node = GetNodeByRow(row, &depth); | |
| 643 if (node) { | |
| 644 gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth)); | |
| 645 if (bounds.Contains(event.location())) { | |
| 646 int relative_x = event.x() - bounds.x(); | |
| 647 if (base::i18n::IsRTL()) | |
| 648 relative_x = bounds.width() - relative_x; | |
| 649 if (relative_x < kArrowRegionSize && | |
| 650 model_->GetChildCount(node->model_node())) { | |
| 651 if (node->is_expanded()) | |
| 652 Collapse(node->model_node()); | |
| 653 else | |
| 654 Expand(node->model_node()); | |
| 655 } else if (relative_x > kArrowRegionSize) { | |
| 656 SetSelectedNode(node->model_node()); | |
| 657 bool should_toggle = false; | |
| 658 if (event.type() == ui::ET_GESTURE_TAP) { | |
| 659 const ui::GestureEvent& gesture = | |
| 660 static_cast<const ui::GestureEvent&>(event); | |
| 661 should_toggle = gesture.details().tap_count() == 2; | |
| 662 } else { | |
| 663 should_toggle = (event.flags() & ui::EF_IS_DOUBLE_CLICK) != 0; | |
| 664 } | |
| 665 if (should_toggle) { | |
| 666 if (node->is_expanded()) | |
| 667 Collapse(node->model_node()); | |
| 668 else | |
| 669 Expand(node->model_node()); | |
| 670 } | |
| 671 } | |
| 672 } | |
| 673 } | |
| 674 return true; | |
| 675 } | |
| 676 | |
| 677 void TreeView::LoadChildren(InternalNode* node) { | |
| 678 DCHECK_EQ(0, node->child_count()); | |
| 679 DCHECK(!node->loaded_children()); | |
| 680 node->set_loaded_children(true); | |
| 681 for (int i = 0, child_count = model_->GetChildCount(node->model_node()); | |
| 682 i < child_count; ++i) { | |
| 683 InternalNode* child = new InternalNode; | |
| 684 ConfigureInternalNode(model_->GetChild(node->model_node(), i), child); | |
| 685 node->Add(child, node->child_count()); | |
| 686 } | |
| 687 } | |
| 688 | |
| 689 void TreeView::ConfigureInternalNode(TreeModelNode* model_node, | |
| 690 InternalNode* node) { | |
| 691 node->Reset(model_node); | |
| 692 UpdateNodeTextWidth(node); | |
| 693 } | |
| 694 | |
| 695 void TreeView::UpdateNodeTextWidth(InternalNode* node) { | |
| 696 int width = 0, height = 0; | |
| 697 gfx::Canvas::SizeStringInt(node->model_node()->GetTitle(), font_list_, | |
| 698 &width, &height, 0, gfx::Canvas::NO_ELLIPSIS); | |
| 699 node->set_text_width(width); | |
| 700 } | |
| 701 | |
| 702 void TreeView::DrawnNodesChanged() { | |
| 703 UpdatePreferredSize(); | |
| 704 PreferredSizeChanged(); | |
| 705 SchedulePaint(); | |
| 706 } | |
| 707 | |
| 708 void TreeView::UpdatePreferredSize() { | |
| 709 preferred_size_ = gfx::Size(); | |
| 710 if (!model_) | |
| 711 return; | |
| 712 | |
| 713 preferred_size_.SetSize( | |
| 714 root_.GetMaxWidth(text_offset_, root_shown_ ? 1 : 0) + | |
| 715 kTextHorizontalPadding * 2, | |
| 716 row_height_ * GetRowCount() + kVerticalInset * 2); | |
| 717 } | |
| 718 | |
| 719 void TreeView::LayoutEditor() { | |
| 720 if (!editing_) | |
| 721 return; | |
| 722 | |
| 723 DCHECK(selected_node_); | |
| 724 // Position the editor so that its text aligns with the text we drew. | |
| 725 gfx::Rect row_bounds = GetBoundsForNode(selected_node_); | |
| 726 row_bounds.set_x( | |
| 727 GetMirroredXWithWidthInView(row_bounds.x(), row_bounds.width())); | |
| 728 row_bounds.set_x(row_bounds.x() + text_offset_); | |
| 729 row_bounds.set_width(row_bounds.width() - text_offset_); | |
| 730 row_bounds.Inset(kTextHorizontalPadding, kTextVerticalPadding); | |
| 731 row_bounds.Inset(-empty_editor_size_.width() / 2, | |
| 732 -(empty_editor_size_.height() - font_list_.GetHeight()) / 2); | |
| 733 // Give a little extra space for editing. | |
| 734 row_bounds.set_width(row_bounds.width() + 50); | |
| 735 editor_->SetBoundsRect(row_bounds); | |
| 736 editor_->Layout(); | |
| 737 } | |
| 738 | |
| 739 void TreeView::SchedulePaintForNode(InternalNode* node) { | |
| 740 if (!node) | |
| 741 return; // Explicitly allow NULL to be passed in. | |
| 742 SchedulePaintInRect(GetBoundsForNode(node)); | |
| 743 } | |
| 744 | |
| 745 void TreeView::PaintRows(gfx::Canvas* canvas, | |
| 746 int min_row, | |
| 747 int max_row, | |
| 748 InternalNode* node, | |
| 749 int depth, | |
| 750 int* row) { | |
| 751 if (*row >= max_row) | |
| 752 return; | |
| 753 | |
| 754 if (*row >= min_row && *row < max_row) | |
| 755 PaintRow(canvas, node, *row, depth); | |
| 756 (*row)++; | |
| 757 if (!node->is_expanded()) | |
| 758 return; | |
| 759 depth++; | |
| 760 for (int i = 0; i < node->child_count() && *row < max_row; ++i) | |
| 761 PaintRows(canvas, min_row, max_row, node->GetChild(i), depth, row); | |
| 762 } | |
| 763 | |
| 764 void TreeView::PaintRow(gfx::Canvas* canvas, | |
| 765 InternalNode* node, | |
| 766 int row, | |
| 767 int depth) { | |
| 768 gfx::Rect bounds(GetBoundsForNodeImpl(node, row, depth)); | |
| 769 | |
| 770 if (model_->GetChildCount(node->model_node())) | |
| 771 PaintExpandControl(canvas, bounds, node->is_expanded()); | |
| 772 | |
| 773 // Paint the icon. | |
| 774 gfx::ImageSkia icon; | |
| 775 int icon_index = model_->GetIconIndex(node->model_node()); | |
| 776 if (icon_index != -1) | |
| 777 icon = icons_[icon_index]; | |
| 778 else if (node == selected_node_) | |
| 779 icon = open_icon_; | |
| 780 else | |
| 781 icon = closed_icon_; | |
| 782 int icon_x = kArrowRegionSize + kImagePadding + | |
| 783 (open_icon_.width() - icon.width()) / 2; | |
| 784 if (base::i18n::IsRTL()) | |
| 785 icon_x = bounds.right() - icon_x - open_icon_.width(); | |
| 786 else | |
| 787 icon_x += bounds.x(); | |
| 788 canvas->DrawImageInt( | |
| 789 icon, icon_x, | |
| 790 bounds.y() + (bounds.height() - icon.height()) / 2); | |
| 791 | |
| 792 if (!editing_ || node != selected_node_) { | |
| 793 gfx::Rect text_bounds(bounds.x() + text_offset_, bounds.y(), | |
| 794 bounds.width() - text_offset_, bounds.height()); | |
| 795 if (base::i18n::IsRTL()) | |
| 796 text_bounds.set_x(bounds.x()); | |
| 797 if (node == selected_node_) { | |
| 798 const SkColor bg_color = GetNativeTheme()->GetSystemColor( | |
| 799 text_background_color_id(HasFocus())); | |
| 800 canvas->FillRect(text_bounds, bg_color); | |
| 801 if (HasFocus()) | |
| 802 canvas->DrawFocusRect(text_bounds); | |
| 803 } | |
| 804 const ui::NativeTheme::ColorId color_id = | |
| 805 text_color_id(HasFocus(), node == selected_node_); | |
| 806 const gfx::Rect internal_bounds( | |
| 807 text_bounds.x() + kTextHorizontalPadding, | |
| 808 text_bounds.y() + kTextVerticalPadding, | |
| 809 text_bounds.width() - kTextHorizontalPadding * 2, | |
| 810 text_bounds.height() - kTextVerticalPadding * 2); | |
| 811 canvas->DrawStringRect(node->model_node()->GetTitle(), font_list_, | |
| 812 GetNativeTheme()->GetSystemColor(color_id), | |
| 813 internal_bounds); | |
| 814 } | |
| 815 } | |
| 816 | |
| 817 void TreeView::PaintExpandControl(gfx::Canvas* canvas, | |
| 818 const gfx::Rect& node_bounds, | |
| 819 bool expanded) { | |
| 820 int center_x; | |
| 821 if (base::i18n::IsRTL()) { | |
| 822 center_x = node_bounds.right() - kArrowRegionSize + | |
| 823 (kArrowRegionSize - 4) / 2; | |
| 824 } else { | |
| 825 center_x = node_bounds.x() + (kArrowRegionSize - 4) / 2; | |
| 826 } | |
| 827 int center_y = node_bounds.y() + node_bounds.height() / 2; | |
| 828 const SkColor arrow_color = GetNativeTheme()->GetSystemColor( | |
| 829 ui::NativeTheme::kColorId_TreeArrow); | |
| 830 // TODO: this should come from an image. | |
| 831 if (!expanded) { | |
| 832 int delta = base::i18n::IsRTL() ? 1 : -1; | |
| 833 for (int i = 0; i < 4; ++i) { | |
| 834 canvas->FillRect(gfx::Rect(center_x + delta * (2 - i), | |
| 835 center_y - (3 - i), 1, (3 - i) * 2 + 1), | |
| 836 arrow_color); | |
| 837 } | |
| 838 } else { | |
| 839 center_y -= 2; | |
| 840 for (int i = 0; i < 4; ++i) { | |
| 841 canvas->FillRect(gfx::Rect(center_x - (3 - i), center_y + i, | |
| 842 (3 - i) * 2 + 1, 1), arrow_color); | |
| 843 } | |
| 844 } | |
| 845 } | |
| 846 | |
| 847 TreeView::InternalNode* TreeView::GetInternalNodeForModelNode( | |
| 848 ui::TreeModelNode* model_node, | |
| 849 GetInternalNodeCreateType create_type) { | |
| 850 if (model_node == root_.model_node()) | |
| 851 return &root_; | |
| 852 InternalNode* parent_internal_node = | |
| 853 GetInternalNodeForModelNode(model_->GetParent(model_node), create_type); | |
| 854 if (!parent_internal_node) | |
| 855 return NULL; | |
| 856 if (!parent_internal_node->loaded_children()) { | |
| 857 if (create_type == DONT_CREATE_IF_NOT_LOADED) | |
| 858 return NULL; | |
| 859 LoadChildren(parent_internal_node); | |
| 860 } | |
| 861 return parent_internal_node->GetChild( | |
| 862 model_->GetIndexOf(parent_internal_node->model_node(), model_node)); | |
| 863 } | |
| 864 | |
| 865 gfx::Rect TreeView::GetBoundsForNode(InternalNode* node) { | |
| 866 int row, depth; | |
| 867 row = GetRowForInternalNode(node, &depth); | |
| 868 return GetBoundsForNodeImpl(node, row, depth); | |
| 869 } | |
| 870 | |
| 871 gfx::Rect TreeView::GetBoundsForNodeImpl(InternalNode* node, | |
| 872 int row, | |
| 873 int depth) { | |
| 874 gfx::Rect rect(depth * kIndent + kHorizontalInset, | |
| 875 row * row_height_ + kVerticalInset, | |
| 876 text_offset_ + node->text_width() + | |
| 877 kTextHorizontalPadding * 2, | |
| 878 row_height_); | |
| 879 rect.set_x(GetMirroredXWithWidthInView(rect.x(), rect.width())); | |
| 880 return rect; | |
| 881 } | |
| 882 | |
| 883 int TreeView::GetRowForInternalNode(InternalNode* node, int* depth) { | |
| 884 DCHECK(!node->parent() || IsExpanded(node->parent()->model_node())); | |
| 885 *depth = -1; | |
| 886 int row = -1; | |
| 887 InternalNode* tmp_node = node; | |
| 888 while (tmp_node->parent()) { | |
| 889 int index_in_parent = tmp_node->parent()->GetIndexOf(tmp_node); | |
| 890 (*depth)++; | |
| 891 row++; // For node. | |
| 892 for (int i = 0; i < index_in_parent; ++i) | |
| 893 row += tmp_node->parent()->GetChild(i)->NumExpandedNodes(); | |
| 894 tmp_node = tmp_node->parent(); | |
| 895 } | |
| 896 if (root_shown_) { | |
| 897 (*depth)++; | |
| 898 row++; | |
| 899 } | |
| 900 return row; | |
| 901 } | |
| 902 | |
| 903 TreeView::InternalNode* TreeView::GetNodeByRow(int row, int* depth) { | |
| 904 int current_row = root_row(); | |
| 905 *depth = 0; | |
| 906 return GetNodeByRowImpl(&root_, row, root_depth(), ¤t_row, depth); | |
| 907 } | |
| 908 | |
| 909 TreeView::InternalNode* TreeView::GetNodeByRowImpl(InternalNode* node, | |
| 910 int target_row, | |
| 911 int current_depth, | |
| 912 int* current_row, | |
| 913 int* node_depth) { | |
| 914 if (*current_row == target_row) { | |
| 915 *node_depth = current_depth; | |
| 916 return node; | |
| 917 } | |
| 918 (*current_row)++; | |
| 919 if (node->is_expanded()) { | |
| 920 current_depth++; | |
| 921 for (int i = 0; i < node->child_count(); ++i) { | |
| 922 InternalNode* result = GetNodeByRowImpl( | |
| 923 node->GetChild(i), target_row, current_depth, current_row, | |
| 924 node_depth); | |
| 925 if (result) | |
| 926 return result; | |
| 927 } | |
| 928 } | |
| 929 return NULL; | |
| 930 } | |
| 931 | |
| 932 void TreeView::IncrementSelection(IncrementType type) { | |
| 933 if (!model_) | |
| 934 return; | |
| 935 | |
| 936 if (!GetSelectedNode()) { | |
| 937 // If nothing is selected select the first or last node. | |
| 938 if (!root_.child_count()) | |
| 939 return; | |
| 940 if (type == INCREMENT_PREVIOUS) { | |
| 941 int row_count = GetRowCount(); | |
| 942 int depth = 0; | |
| 943 DCHECK(row_count); | |
| 944 InternalNode* node = GetNodeByRow(row_count - 1, &depth); | |
| 945 SetSelectedNode(node->model_node()); | |
| 946 } else if (root_shown_) { | |
| 947 SetSelectedNode(root_.model_node()); | |
| 948 } else { | |
| 949 SetSelectedNode(root_.GetChild(0)->model_node()); | |
| 950 } | |
| 951 return; | |
| 952 } | |
| 953 | |
| 954 int depth = 0; | |
| 955 int delta = type == INCREMENT_PREVIOUS ? -1 : 1; | |
| 956 int row = GetRowForInternalNode(selected_node_, &depth); | |
| 957 int new_row = std::min(GetRowCount() - 1, std::max(0, row + delta)); | |
| 958 if (new_row == row) | |
| 959 return; // At the end/beginning. | |
| 960 SetSelectedNode(GetNodeByRow(new_row, &depth)->model_node()); | |
| 961 } | |
| 962 | |
| 963 void TreeView::CollapseOrSelectParent() { | |
| 964 if (selected_node_) { | |
| 965 if (selected_node_->is_expanded()) | |
| 966 Collapse(selected_node_->model_node()); | |
| 967 else if (selected_node_->parent()) | |
| 968 SetSelectedNode(selected_node_->parent()->model_node()); | |
| 969 } | |
| 970 } | |
| 971 | |
| 972 void TreeView::ExpandOrSelectChild() { | |
| 973 if (selected_node_) { | |
| 974 if (!selected_node_->is_expanded()) | |
| 975 Expand(selected_node_->model_node()); | |
| 976 else if (selected_node_->child_count()) | |
| 977 SetSelectedNode(selected_node_->GetChild(0)->model_node()); | |
| 978 } | |
| 979 } | |
| 980 | |
| 981 bool TreeView::ExpandImpl(TreeModelNode* model_node) { | |
| 982 TreeModelNode* parent = model_->GetParent(model_node); | |
| 983 if (!parent) { | |
| 984 // Node should be the root. | |
| 985 DCHECK_EQ(root_.model_node(), model_node); | |
| 986 bool was_expanded = root_.is_expanded(); | |
| 987 root_.set_is_expanded(true); | |
| 988 return !was_expanded; | |
| 989 } | |
| 990 | |
| 991 // Expand all the parents. | |
| 992 bool return_value = ExpandImpl(parent); | |
| 993 InternalNode* internal_node = | |
| 994 GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED); | |
| 995 DCHECK(internal_node); | |
| 996 if (!internal_node->is_expanded()) { | |
| 997 if (!internal_node->loaded_children()) | |
| 998 LoadChildren(internal_node); | |
| 999 internal_node->set_is_expanded(true); | |
| 1000 return_value = true; | |
| 1001 } | |
| 1002 return return_value; | |
| 1003 } | |
| 1004 | |
| 1005 // InternalNode ---------------------------------------------------------------- | |
| 1006 | |
| 1007 TreeView::InternalNode::InternalNode() | |
| 1008 : model_node_(NULL), | |
| 1009 loaded_children_(false), | |
| 1010 is_expanded_(false), | |
| 1011 text_width_(0) { | |
| 1012 } | |
| 1013 | |
| 1014 TreeView::InternalNode::~InternalNode() { | |
| 1015 } | |
| 1016 | |
| 1017 void TreeView::InternalNode::Reset(ui::TreeModelNode* node) { | |
| 1018 model_node_ = node; | |
| 1019 loaded_children_ = false; | |
| 1020 is_expanded_ = false; | |
| 1021 text_width_ = 0; | |
| 1022 } | |
| 1023 | |
| 1024 int TreeView::InternalNode::NumExpandedNodes() const { | |
| 1025 int result = 1; // For this. | |
| 1026 if (!is_expanded_) | |
| 1027 return result; | |
| 1028 for (int i = 0; i < child_count(); ++i) | |
| 1029 result += GetChild(i)->NumExpandedNodes(); | |
| 1030 return result; | |
| 1031 } | |
| 1032 | |
| 1033 int TreeView::InternalNode::GetMaxWidth(int indent, int depth) { | |
| 1034 int max_width = text_width_ + indent * depth; | |
| 1035 if (!is_expanded_) | |
| 1036 return max_width; | |
| 1037 for (int i = 0; i < child_count(); ++i) { | |
| 1038 max_width = std::max(max_width, | |
| 1039 GetChild(i)->GetMaxWidth(indent, depth + 1)); | |
| 1040 } | |
| 1041 return max_width; | |
| 1042 } | |
| 1043 | |
| 1044 } // namespace views | |
| OLD | NEW |