Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(469)

Side by Side Diff: views/controls/tree/tree_view.cc

Issue 8655001: views: Move table and tree directories to ui/views/controls/. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: exclude native_widget_win_unittest too Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « views/controls/tree/tree_view.h ('k') | views/views.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "views/controls/tree/tree_view.h"
6
7 #include <vector>
8
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #include "base/stl_util.h"
12 #include "base/win/win_util.h"
13 #include "grit/ui_resources.h"
14 #include "ui/base/accessibility/accessible_view_state.h"
15 #include "ui/base/keycodes/keyboard_code_conversion_win.h"
16 #include "ui/base/keycodes/keyboard_codes.h"
17 #include "ui/base/l10n/l10n_util_win.h"
18 #include "ui/base/models/accelerator.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/base/win/hwnd_util.h"
21 #include "ui/gfx/canvas_skia.h"
22 #include "ui/gfx/canvas_skia_paint.h"
23 #include "ui/gfx/favicon_size.h"
24 #include "ui/gfx/icon_util.h"
25 #include "ui/gfx/point.h"
26 #include "ui/views/focus/focus_manager.h"
27 #include "ui/views/widget/widget.h"
28
29 using ui::TreeModel;
30 using ui::TreeModelNode;
31
32 namespace views {
33
34 TreeView::TreeView()
35 : tree_view_(NULL),
36 model_(NULL),
37 auto_expand_children_(false),
38 editable_(true),
39 next_id_(0),
40 controller_(NULL),
41 editing_node_(NULL),
42 root_shown_(true),
43 lines_at_root_(false),
44 process_enter_(false),
45 show_context_menu_only_when_node_selected_(true),
46 select_on_right_mouse_down_(true),
47 ALLOW_THIS_IN_INITIALIZER_LIST(wrapper_(this)),
48 original_handler_(NULL),
49 drag_enabled_(false),
50 observer_added_(false),
51 has_custom_icons_(false),
52 image_list_(NULL) {
53 }
54
55 TreeView::~TreeView() {
56 Cleanup();
57 }
58
59 void TreeView::GetAccessibleState(ui::AccessibleViewState* state) {
60 state->role = ui::AccessibilityTypes::ROLE_OUTLINE;
61 state->state = ui::AccessibilityTypes::STATE_READONLY;
62 }
63
64 void TreeView::SetModel(TreeModel* model) {
65 if (model == model_)
66 return;
67 if (model_ && tree_view_)
68 DeleteRootItems();
69
70 RemoveObserverFromModel();
71
72 model_ = model;
73 if (tree_view_ && model_) {
74 CreateRootItems();
75 AddObserverToModel();
76 HIMAGELIST last_image_list = image_list_;
77 image_list_ = CreateImageList();
78 TreeView_SetImageList(tree_view_, image_list_, TVSIL_NORMAL);
79 if (last_image_list)
80 ImageList_Destroy(last_image_list);
81 }
82 }
83
84 // Sets whether the user can edit the nodes. The default is true.
85 void TreeView::SetEditable(bool editable) {
86 if (editable == editable_)
87 return;
88 editable_ = editable;
89 if (!tree_view_)
90 return;
91 LONG_PTR style = GetWindowLongPtr(tree_view_, GWL_STYLE);
92 style &= ~TVS_EDITLABELS;
93 SetWindowLongPtr(tree_view_, GWL_STYLE, style);
94 }
95
96 void TreeView::StartEditing(TreeModelNode* node) {
97 DCHECK(node && tree_view_);
98 // Cancel the current edit.
99 CancelEdit();
100 // Make sure all ancestors are expanded.
101 if (model_->GetParent(node))
102 Expand(model_->GetParent(node));
103 const NodeDetails* details = GetNodeDetails(node);
104 // Tree needs focus for editing to work.
105 SetFocus(tree_view_);
106 // Select the node, else if the user commits the edit the selection reverts.
107 SetSelectedNode(node);
108 TreeView_EditLabel(tree_view_, details->tree_item);
109 }
110
111 void TreeView::CancelEdit() {
112 DCHECK(tree_view_);
113 TreeView_EndEditLabelNow(tree_view_, TRUE);
114 }
115
116 void TreeView::CommitEdit() {
117 DCHECK(tree_view_);
118 TreeView_EndEditLabelNow(tree_view_, FALSE);
119 }
120
121 TreeModelNode* TreeView::GetEditingNode() {
122 // I couldn't find a way to dynamically query for this, so it is cached.
123 return editing_node_;
124 }
125
126 void TreeView::SetSelectedNode(TreeModelNode* node) {
127 DCHECK(tree_view_);
128 if (!node) {
129 TreeView_SelectItem(tree_view_, NULL);
130 return;
131 }
132 if (node != model_->GetRoot())
133 Expand(model_->GetParent(node));
134 if (!root_shown_ && node == model_->GetRoot()) {
135 // If the root isn't shown, we can't select it, clear out the selection
136 // instead.
137 TreeView_SelectItem(tree_view_, NULL);
138 } else {
139 // Select the node and make sure it is visible.
140 TreeView_SelectItem(tree_view_, GetNodeDetails(node)->tree_item);
141 }
142 }
143
144 TreeModelNode* TreeView::GetSelectedNode() {
145 if (!tree_view_)
146 return NULL;
147 HTREEITEM selected_item = TreeView_GetSelection(tree_view_);
148 if (!selected_item)
149 return NULL;
150 NodeDetails* details = GetNodeDetailsByTreeItem(selected_item);
151 DCHECK(details);
152 return details->node;
153 }
154
155 void TreeView::Expand(TreeModelNode* node) {
156 DCHECK(model_ && node);
157 if (!root_shown_ && model_->GetRoot() == node) {
158 // Can only expand the root if it is showing.
159 return;
160 }
161 TreeModelNode* parent = model_->GetParent(node);
162 if (parent) {
163 // Make sure all the parents are expanded.
164 Expand(parent);
165 }
166 // And expand this item.
167 TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND);
168 }
169
170 void TreeView::ExpandAll(TreeModelNode* node) {
171 DCHECK(node);
172 // Expand the node.
173 if (node != model_->GetRoot() || root_shown_)
174 TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND);
175 // And recursively expand all the children.
176 for (int i = model_->GetChildCount(node) - 1; i >= 0; --i) {
177 TreeModelNode* child = model_->GetChild(node, i);
178 ExpandAll(child);
179 }
180 }
181
182 bool TreeView::IsExpanded(TreeModelNode* node) {
183 if (!tree_view_)
184 return false;
185 TreeModelNode* parent = model_->GetParent(node);
186 if (!parent)
187 return true;
188 if (!IsExpanded(parent))
189 return false;
190 NodeDetails* details = GetNodeDetails(node);
191 return (TreeView_GetItemState(tree_view_, details->tree_item, TVIS_EXPANDED) &
192 TVIS_EXPANDED) != 0;
193 }
194
195 void TreeView::SetRootShown(bool root_shown) {
196 if (root_shown_ == root_shown)
197 return;
198 root_shown_ = root_shown;
199 if (!model_ || !tree_view_)
200 return;
201 // Repopulate the tree.
202 DeleteRootItems();
203 CreateRootItems();
204 }
205
206 void TreeView::TreeNodesAdded(TreeModel* model,
207 TreeModelNode* parent,
208 int start,
209 int count) {
210 DCHECK(parent && start >= 0 && count > 0);
211 if (node_to_details_map_.find(parent) == node_to_details_map_.end() &&
212 (root_shown_ || parent != model_->GetRoot())) {
213 // User hasn't navigated to this entry yet. Ignore the change.
214 return;
215 }
216 HTREEITEM parent_tree_item = NULL;
217 if (root_shown_ || parent != model_->GetRoot()) {
218 const NodeDetails* details = GetNodeDetails(parent);
219 if (!details->loaded_children) {
220 if (count == model_->GetChildCount(parent)) {
221 // Reset the treeviews child count. This triggers the treeview to call
222 // us back.
223 TV_ITEM tv_item = {0};
224 tv_item.mask = TVIF_CHILDREN;
225 tv_item.cChildren = count;
226 tv_item.hItem = details->tree_item;
227 TreeView_SetItem(tree_view_, &tv_item);
228 }
229
230 // Ignore the change, we haven't actually created entries in the tree
231 // for the children.
232 return;
233 }
234 parent_tree_item = details->tree_item;
235 }
236
237 // The user has expanded this node, add the items to it.
238 for (int i = 0; i < count; ++i) {
239 if (i == 0 && start == 0) {
240 CreateItem(parent_tree_item, TVI_FIRST, model_->GetChild(parent, 0));
241 } else {
242 TreeModelNode* previous_sibling = model_->GetChild(parent, i + start - 1);
243 CreateItem(parent_tree_item,
244 GetNodeDetails(previous_sibling)->tree_item,
245 model_->GetChild(parent, i + start));
246 }
247 }
248 }
249
250 void TreeView::TreeNodesRemoved(TreeModel* model,
251 TreeModelNode* parent,
252 int start,
253 int count) {
254 DCHECK(parent && start >= 0 && count > 0);
255
256 HTREEITEM tree_item;
257 if (!root_shown_ && parent == model->GetRoot()) {
258 // NOTE: we can't call GetTreeItemForNodeDuringMutation here as in this
259 // configuration the root has no treeitem.
260 tree_item = TreeView_GetRoot(tree_view_);
261 } else {
262 HTREEITEM parent_tree_item = GetTreeItemForNodeDuringMutation(parent);
263 if (!parent_tree_item)
264 return;
265
266 tree_item = TreeView_GetChild(tree_view_, parent_tree_item);
267 }
268
269 // Find the last item. Windows doesn't offer a convenient way to get the
270 // TREEITEM at a particular index, so we iterate.
271 for (int i = 0; i < (start + count - 1); ++i) {
272 tree_item = TreeView_GetNextSibling(tree_view_, tree_item);
273 }
274
275 // NOTE: the direction doesn't matter here. I've made it backwards to
276 // reinforce we're deleting from the end forward.
277 for (int i = count - 1; i >= 0; --i) {
278 HTREEITEM previous = (start + i) > 0 ?
279 TreeView_GetPrevSibling(tree_view_, tree_item) : NULL;
280 RecursivelyDelete(GetNodeDetailsByTreeItem(tree_item));
281 tree_item = previous;
282 }
283 }
284
285 void TreeView::TreeNodeChanged(TreeModel* model, TreeModelNode* node) {
286 if (node_to_details_map_.find(node) == node_to_details_map_.end()) {
287 // User hasn't navigated to this entry yet. Ignore the change.
288 return;
289 }
290 const NodeDetails* details = GetNodeDetails(node);
291 TV_ITEM tv_item = {0};
292 tv_item.mask = TVIF_TEXT;
293 tv_item.hItem = details->tree_item;
294 tv_item.pszText = LPSTR_TEXTCALLBACK;
295 TreeView_SetItem(tree_view_, &tv_item);
296 }
297
298 gfx::Point TreeView::GetKeyboardContextMenuLocation() {
299 int y = height() / 2;
300 if (GetSelectedNode()) {
301 RECT bounds;
302 RECT client_rect;
303 if (TreeView_GetItemRect(tree_view_,
304 GetNodeDetails(GetSelectedNode())->tree_item,
305 &bounds, TRUE) &&
306 GetClientRect(tree_view_, &client_rect) &&
307 bounds.bottom >= 0 && bounds.bottom < client_rect.bottom) {
308 y = bounds.bottom;
309 }
310 }
311 gfx::Point screen_loc(0, y);
312 if (base::i18n::IsRTL())
313 screen_loc.set_x(width());
314 ConvertPointToScreen(this, &screen_loc);
315 return screen_loc;
316 }
317
318 HWND TreeView::CreateNativeControl(HWND parent_container) {
319 int style = WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS;
320 if (!drag_enabled_)
321 style |= TVS_DISABLEDRAGDROP;
322 if (editable_)
323 style |= TVS_EDITLABELS;
324 if (lines_at_root_)
325 style |= TVS_LINESATROOT;
326 tree_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(),
327 WC_TREEVIEW,
328 L"",
329 style,
330 0, 0, width(), height(),
331 parent_container, NULL, NULL, NULL);
332 ui::CheckWindowCreated(tree_view_);
333 SetWindowLongPtr(tree_view_, GWLP_USERDATA,
334 reinterpret_cast<LONG_PTR>(&wrapper_));
335 original_handler_ = ui::SetWindowProc(tree_view_, &TreeWndProc);
336 l10n_util::AdjustUIFontForWindow(tree_view_);
337
338 if (model_) {
339 CreateRootItems();
340 AddObserverToModel();
341 image_list_ = CreateImageList();
342 TreeView_SetImageList(tree_view_, image_list_, TVSIL_NORMAL);
343 }
344
345 // Bug 964884: detach the IME attached to this window.
346 // We should attach IMEs only when we need to input CJK strings.
347 ::ImmAssociateContextEx(tree_view_, NULL, 0);
348 return tree_view_;
349 }
350
351 LRESULT TreeView::OnNotify(int w_param, LPNMHDR l_param) {
352 switch (l_param->code) {
353 case TVN_GETDISPINFO: {
354 // Windows is requesting more information about an item.
355 // WARNING: At the time this is called the tree_item of the NodeDetails
356 // in the maps is NULL.
357 DCHECK(model_);
358 NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
359
360 // WARNING: its possible for Windows to send a TVN_GETDISPINFO message
361 // after the WM_DESTROY time of the native control. Since the details
362 // map will be cleaned up on OnDestroy(), don't try to access it in
363 // this case.
364 if (!id_to_details_map_.empty()) {
365 const NodeDetails* details =
366 GetNodeDetailsByID(static_cast<int>(info->item.lParam));
367 if (info->item.mask & TVIF_CHILDREN)
368 info->item.cChildren = model_->GetChildCount(details->node);
369 if (info->item.mask & TVIF_TEXT) {
370 DCHECK(info->item.cchTextMax);
371
372 string16 text = details->node->GetTitle();
373 // Adjust the string direction if such adjustment is required.
374 base::i18n::AdjustStringForLocaleDirection(&text);
375
376 wcsncpy_s(info->item.pszText, info->item.cchTextMax, text.c_str(),
377 _TRUNCATE);
378 }
379 // Instructs windows to cache the values for this node.
380 info->item.mask |= TVIF_DI_SETITEM;
381 } else {
382 if (info->item.mask & TVIF_CHILDREN)
383 info->item.cChildren = 0;
384
385 if (info->item.mask & TVIF_TEXT)
386 wcsncpy_s(info->item.pszText, info->item.cchTextMax, L"", _TRUNCATE);
387 }
388
389 // Return value ignored.
390 return 0;
391 }
392
393 case TVN_ITEMEXPANDING: {
394 // Notification that a node is expanding. If we haven't populated the
395 // tree view with the contents of the model, we do it here.
396 DCHECK(model_);
397 NMTREEVIEW* info = reinterpret_cast<NMTREEVIEW*>(l_param);
398 NodeDetails* details =
399 GetNodeDetailsByID(static_cast<int>(info->itemNew.lParam));
400 if (!details->loaded_children) {
401 details->loaded_children = true;
402 for (int i = 0; i < model_->GetChildCount(details->node); ++i) {
403 CreateItem(details->tree_item, TVI_LAST,
404 model_->GetChild(details->node, i));
405 if (auto_expand_children_)
406 Expand(model_->GetChild(details->node, i));
407 }
408 }
409 // Return FALSE to allow the item to be expanded.
410 return FALSE;
411 }
412
413 case TVN_SELCHANGED:
414 if (controller_)
415 controller_->OnTreeViewSelectionChanged(this);
416 break;
417
418 case TVN_BEGINLABELEDIT: {
419 NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
420 NodeDetails* details =
421 GetNodeDetailsByID(static_cast<int>(info->item.lParam));
422 // Return FALSE to allow editing.
423 if (!controller_ || controller_->CanEdit(this, details->node)) {
424 editing_node_ = details->node;
425 return FALSE;
426 }
427 return TRUE;
428 }
429
430 case TVN_ENDLABELEDIT: {
431 NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
432 if (info->item.pszText) {
433 // User accepted edit.
434 NodeDetails* details =
435 GetNodeDetailsByID(static_cast<int>(info->item.lParam));
436 model_->SetTitle(details->node, info->item.pszText);
437 editing_node_ = NULL;
438 // Return FALSE so that the tree item doesn't change its text (if the
439 // model changed the value, it should have sent out notification which
440 // will have updated the value).
441 return FALSE;
442 }
443 editing_node_ = NULL;
444 // Return value ignored.
445 return 0;
446 }
447
448 case TVN_KEYDOWN:
449 if (controller_) {
450 NMTVKEYDOWN* key_down_message =
451 reinterpret_cast<NMTVKEYDOWN*>(l_param);
452 controller_->OnTreeViewKeyDown(
453 ui::KeyboardCodeForWindowsKeyCode(key_down_message->wVKey));
454 }
455 break;
456
457 default:
458 break;
459 }
460 return 0;
461 }
462
463 void TreeView::OnDestroy() {
464 Cleanup();
465 }
466
467 bool TreeView::OnKeyDown(ui::KeyboardCode virtual_key_code) {
468 if (virtual_key_code == VK_F2) {
469 if (!GetEditingNode()) {
470 TreeModelNode* selected_node = GetSelectedNode();
471 if (selected_node)
472 StartEditing(selected_node);
473 }
474 return true;
475 } else if (virtual_key_code == ui::VKEY_RETURN && !process_enter_) {
476 Widget* widget = GetWidget();
477 DCHECK(widget);
478 ui::Accelerator accelerator(ui::Accelerator(virtual_key_code,
479 base::win::IsShiftPressed(),
480 base::win::IsCtrlPressed(),
481 base::win::IsAltPressed()));
482 GetFocusManager()->ProcessAccelerator(accelerator);
483 return true;
484 }
485 return false;
486 }
487
488 void TreeView::OnContextMenu(const POINT& location) {
489 if (!context_menu_controller())
490 return;
491
492 if (location.x == -1 && location.y == -1) {
493 // Let NativeControl's implementation handle keyboard gesture.
494 NativeControl::OnContextMenu(location);
495 return;
496 }
497
498 if (show_context_menu_only_when_node_selected_) {
499 if (!GetSelectedNode())
500 return;
501
502 // Make sure the mouse is over the selected node.
503 TVHITTESTINFO hit_info;
504 gfx::Point local_loc(location);
505 ConvertPointToView(NULL, this, &local_loc);
506 hit_info.pt = local_loc.ToPOINT();
507 HTREEITEM hit_item = TreeView_HitTest(tree_view_, &hit_info);
508 if (!hit_item ||
509 GetNodeDetails(GetSelectedNode())->tree_item != hit_item ||
510 (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT |
511 TVHT_ONITEMINDENT)) == 0) {
512 return;
513 }
514 }
515 ShowContextMenu(gfx::Point(location), true);
516 }
517
518 TreeModelNode* TreeView::GetNodeForTreeItem(HTREEITEM tree_item) {
519 NodeDetails* details = GetNodeDetailsByTreeItem(tree_item);
520 return details ? details->node : NULL;
521 }
522
523 HTREEITEM TreeView::GetTreeItemForNode(TreeModelNode* node) {
524 NodeDetails* details = GetNodeDetails(node);
525 return details ? details->tree_item : NULL;
526 }
527
528 void TreeView::Cleanup() {
529 RemoveObserverFromModel();
530
531 // Both node_to_details_map_ and node_to_details_map_ have the same value,
532 // as such only need to delete from one.
533 STLDeleteContainerPairSecondPointers(id_to_details_map_.begin(),
534 id_to_details_map_.end());
535 id_to_details_map_.clear();
536 node_to_details_map_.clear();
537
538 if (image_list_) {
539 ImageList_Destroy(image_list_);
540 image_list_ = NULL;
541 }
542 }
543
544 void TreeView::AddObserverToModel() {
545 if (model_ && !observer_added_) {
546 model_->AddObserver(this);
547 observer_added_ = true;
548 }
549 }
550
551 void TreeView::RemoveObserverFromModel() {
552 if (model_ && observer_added_) {
553 model_->RemoveObserver(this);
554 observer_added_ = false;
555 }
556 }
557
558 void TreeView::DeleteRootItems() {
559 HTREEITEM root = TreeView_GetRoot(tree_view_);
560 if (root) {
561 if (root_shown_) {
562 RecursivelyDelete(GetNodeDetailsByTreeItem(root));
563 } else {
564 do {
565 RecursivelyDelete(GetNodeDetailsByTreeItem(root));
566 } while ((root = TreeView_GetRoot(tree_view_)));
567 }
568 }
569 }
570
571 void TreeView::CreateRootItems() {
572 DCHECK(model_);
573 DCHECK(tree_view_);
574 TreeModelNode* root = model_->GetRoot();
575 if (root_shown_) {
576 CreateItem(NULL, TVI_LAST, root);
577 } else {
578 for (int i = 0; i < model_->GetChildCount(root); ++i)
579 CreateItem(NULL, TVI_LAST, model_->GetChild(root, i));
580 }
581 }
582
583 void TreeView::CreateItem(HTREEITEM parent_item,
584 HTREEITEM after,
585 TreeModelNode* node) {
586 DCHECK(node);
587 TVINSERTSTRUCT insert_struct = {0};
588 insert_struct.hParent = parent_item;
589 insert_struct.hInsertAfter = after;
590 insert_struct.itemex.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_TEXT |
591 TVIF_SELECTEDIMAGE | TVIF_IMAGE;
592 // Call us back for the text.
593 insert_struct.itemex.pszText = LPSTR_TEXTCALLBACK;
594 // And the number of children.
595 insert_struct.itemex.cChildren = I_CHILDRENCALLBACK;
596 // Set the index of the icons to use. These are relative to the imagelist
597 // created in CreateImageList.
598 int icon_index = model_->GetIconIndex(node);
599 if (icon_index == -1) {
600 insert_struct.itemex.iImage = 0;
601 insert_struct.itemex.iSelectedImage = 1;
602 } else {
603 // The first two images are the default ones.
604 insert_struct.itemex.iImage = icon_index + 2;
605 insert_struct.itemex.iSelectedImage = icon_index + 2;
606 }
607 int node_id = next_id_++;
608 insert_struct.itemex.lParam = node_id;
609
610 // Invoking TreeView_InsertItem triggers OnNotify to be called. As such,
611 // we set the map entries before adding the item.
612 NodeDetails* node_details = new NodeDetails(node_id, node);
613
614 DCHECK(node_to_details_map_.count(node) == 0);
615 DCHECK(id_to_details_map_.count(node_id) == 0);
616
617 node_to_details_map_[node] = node_details;
618 id_to_details_map_[node_id] = node_details;
619
620 node_details->tree_item = TreeView_InsertItem(tree_view_, &insert_struct);
621 }
622
623 void TreeView::RecursivelyDelete(NodeDetails* node) {
624 DCHECK(node);
625 HTREEITEM item = node->tree_item;
626 DCHECK(item);
627
628 // Recurse through children.
629 for (HTREEITEM child = TreeView_GetChild(tree_view_, item); child ;) {
630 HTREEITEM next = TreeView_GetNextSibling(tree_view_, child);
631 RecursivelyDelete(GetNodeDetailsByTreeItem(child));
632 child = next;
633 }
634
635 TreeView_DeleteItem(tree_view_, item);
636
637 // finally, it is safe to delete the data for this node.
638 id_to_details_map_.erase(node->id);
639 node_to_details_map_.erase(node->node);
640 delete node;
641 }
642
643 TreeView::NodeDetails* TreeView::GetNodeDetails(TreeModelNode* node) {
644 DCHECK(node &&
645 node_to_details_map_.find(node) != node_to_details_map_.end());
646 return node_to_details_map_[node];
647 }
648
649 // Returns the NodeDetails by identifier (lparam of the HTREEITEM).
650 TreeView::NodeDetails* TreeView::GetNodeDetailsByID(int id) {
651 DCHECK(id_to_details_map_.find(id) != id_to_details_map_.end());
652 return id_to_details_map_[id];
653 }
654
655 TreeView::NodeDetails* TreeView::GetNodeDetailsByTreeItem(HTREEITEM tree_item) {
656 DCHECK(tree_view_ && tree_item);
657 TV_ITEM tv_item = {0};
658 tv_item.hItem = tree_item;
659 tv_item.mask = TVIF_PARAM;
660 if (TreeView_GetItem(tree_view_, &tv_item))
661 return GetNodeDetailsByID(static_cast<int>(tv_item.lParam));
662 return NULL;
663 }
664
665 HIMAGELIST TreeView::CreateImageList() {
666 std::vector<SkBitmap> model_images;
667 model_->GetIcons(&model_images);
668
669 bool rtl = base::i18n::IsRTL();
670 // Creates the default image list used for trees.
671 SkBitmap* closed_icon =
672 ResourceBundle::GetSharedInstance().GetBitmapNamed(
673 (rtl ? IDR_FOLDER_CLOSED_RTL : IDR_FOLDER_CLOSED));
674 SkBitmap* opened_icon =
675 ResourceBundle::GetSharedInstance().GetBitmapNamed(
676 (rtl ? IDR_FOLDER_OPEN_RTL : IDR_FOLDER_OPEN));
677 int width = closed_icon->width();
678 int height = closed_icon->height();
679 DCHECK(opened_icon->width() == width && opened_icon->height() == height);
680 HIMAGELIST image_list =
681 ImageList_Create(width, height, ILC_COLOR32, model_images.size() + 2,
682 model_images.size() + 2);
683 if (image_list) {
684 // NOTE: the order the images are added in effects the selected
685 // image index when adding items to the tree. If you change the
686 // order you'll undoubtedly need to update itemex.iSelectedImage
687 // when the item is added.
688 HICON h_closed_icon = IconUtil::CreateHICONFromSkBitmap(*closed_icon);
689 HICON h_opened_icon = IconUtil::CreateHICONFromSkBitmap(*opened_icon);
690 ImageList_AddIcon(image_list, h_closed_icon);
691 ImageList_AddIcon(image_list, h_opened_icon);
692 DestroyIcon(h_closed_icon);
693 DestroyIcon(h_opened_icon);
694 for (size_t i = 0; i < model_images.size(); ++i) {
695 HICON model_icon;
696
697 // Need to resize the provided icons to be the same size as
698 // IDR_FOLDER_CLOSED if they aren't already.
699 if (model_images[i].width() != width ||
700 model_images[i].height() != height) {
701 gfx::CanvasSkia canvas(width, height, false);
702 // Make the background completely transparent.
703 canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
704
705 // Draw our icons into this canvas.
706 int height_offset = (height - model_images[i].height()) / 2;
707 int width_offset = (width - model_images[i].width()) / 2;
708 canvas.DrawBitmapInt(model_images[i], width_offset, height_offset);
709 model_icon = IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap());
710 } else {
711 model_icon = IconUtil::CreateHICONFromSkBitmap(model_images[i]);
712 }
713 ImageList_AddIcon(image_list, model_icon);
714 DestroyIcon(model_icon);
715 }
716 }
717 return image_list;
718 }
719
720 HTREEITEM TreeView::GetTreeItemForNodeDuringMutation(TreeModelNode* node) {
721 if (node_to_details_map_.find(node) == node_to_details_map_.end()) {
722 // User hasn't navigated to this entry yet. Ignore the change.
723 return NULL;
724 }
725 if (!root_shown_ || node != model_->GetRoot()) {
726 const NodeDetails* details = GetNodeDetails(node);
727 if (!details->loaded_children)
728 return NULL;
729 return details->tree_item;
730 }
731 return TreeView_GetRoot(tree_view_);
732 }
733
734 LRESULT CALLBACK TreeView::TreeWndProc(HWND window,
735 UINT message,
736 WPARAM w_param,
737 LPARAM l_param) {
738 TreeViewWrapper* wrapper = reinterpret_cast<TreeViewWrapper*>(
739 GetWindowLongPtr(window, GWLP_USERDATA));
740 DCHECK(wrapper);
741 TreeView* tree = wrapper->tree_view;
742
743 // We handle the messages WM_ERASEBKGND and WM_PAINT such that we paint into
744 // a DIB first and then perform a BitBlt from the DIB into the underlying
745 // window's DC. This double buffering code prevents the tree view from
746 // flickering during resize.
747 switch (message) {
748 case WM_ERASEBKGND:
749 return 1;
750
751 case WM_PAINT: {
752 gfx::CanvasSkiaPaint canvas(window);
753 if (canvas.isEmpty())
754 return 0;
755
756 HDC dc = skia::BeginPlatformPaint(canvas.sk_canvas());
757 if (base::i18n::IsRTL()) {
758 // gfx::CanvasSkia ends up configuring the DC with a mode of
759 // GM_ADVANCED. For some reason a graphics mode of ADVANCED triggers
760 // all the text to be mirrored when RTL. Set the mode back to COMPATIBLE
761 // and explicitly set the layout. Additionally SetWorldTransform and
762 // COMPATIBLE don't play nicely together. We need to use
763 // SetViewportOrgEx when using a mode of COMPATIBLE.
764 //
765 // Reset the transform to the identify transform. Even though
766 // SetWorldTransform and COMPATIBLE don't play nicely, bits of the
767 // transform still carry over when we set the mode.
768 XFORM xform = {0};
769 xform.eM11 = xform.eM22 = 1;
770 SetWorldTransform(dc, &xform);
771
772 // Set the mode and layout.
773 SetGraphicsMode(dc, GM_COMPATIBLE);
774 SetLayout(dc, LAYOUT_RTL);
775
776 // Transform the viewport such that the origin of the dc is that of
777 // the dirty region. This way when we invoke WM_PRINTCLIENT tree-view
778 // draws the dirty region at the origin of the DC so that when we
779 // copy the bits everything lines up nicely. Without this we end up
780 // copying the upper-left corner to the redraw region.
781 SetViewportOrgEx(dc, -canvas.paintStruct().rcPaint.left,
782 -canvas.paintStruct().rcPaint.top, NULL);
783 }
784 SendMessage(window, WM_PRINTCLIENT, reinterpret_cast<WPARAM>(dc), 0);
785 if (base::i18n::IsRTL()) {
786 // Reset the origin of the dc back to 0. This way when we copy the bits
787 // over we copy the right bits.
788 SetViewportOrgEx(dc, 0, 0, NULL);
789 }
790 skia::EndPlatformPaint(canvas.sk_canvas());
791 return 0;
792 }
793
794 case WM_RBUTTONDOWN:
795 if (tree->select_on_right_mouse_down_) {
796 TVHITTESTINFO hit_info;
797 hit_info.pt = gfx::Point(l_param).ToPOINT();
798 HTREEITEM hit_item = TreeView_HitTest(window, &hit_info);
799 if (hit_item && (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT |
800 TVHT_ONITEMINDENT)) != 0)
801 TreeView_SelectItem(tree->tree_view_, hit_item);
802 }
803 // Fall through and let the default handler process as well.
804 break;
805 }
806 WNDPROC handler = tree->original_handler_;
807 DCHECK(handler);
808 return CallWindowProc(handler, window, message, w_param, l_param);
809 }
810
811 } // namespace views
OLDNEW
« no previous file with comments | « views/controls/tree/tree_view.h ('k') | views/views.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698