summaryrefslogtreecommitdiffstats
path: root/chrome/views/tree_view.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/views/tree_view.cc')
-rw-r--r--chrome/views/tree_view.cc616
1 files changed, 616 insertions, 0 deletions
diff --git a/chrome/views/tree_view.cc b/chrome/views/tree_view.cc
new file mode 100644
index 0000000..f4e43e1
--- /dev/null
+++ b/chrome/views/tree_view.cc
@@ -0,0 +1,616 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/views/tree_view.h"
+
+#include <shellapi.h>
+
+#include "base/win_util.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/gfx/icon_util.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/common/stl_util-inl.h"
+#include "chrome/views/focus_manager.h"
+
+namespace ChromeViews {
+
+static HIMAGELIST tree_image_list_ = NULL;
+
+// Creates the default image list used for trees. The image list is populated
+// from the shell's icons.
+static HIMAGELIST CreateDefaultImageList(bool rtl) {
+ SkBitmap* closed_icon =
+ ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ (rtl ? IDR_FOLDER_CLOSED_RTL : IDR_FOLDER_CLOSED));
+ SkBitmap* opened_icon =
+ ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ (rtl ? IDR_FOLDER_OPEN_RTL : IDR_FOLDER_OPEN));
+ int width = closed_icon->width();
+ int height = closed_icon->height();
+ DCHECK(opened_icon->width() == width && opened_icon->height() == height);
+ HIMAGELIST image_list = ImageList_Create(width, height, ILC_COLOR32, 2, 2);
+ if (image_list) {
+ // NOTE: the order the images are added in effects the selected
+ // image index when adding items to the tree. If you change the
+ // order you'll undoubtedly need to update itemex.iSelectedImage
+ // when the item is added.
+ HICON h_closed_icon = IconUtil::CreateHICONFromSkBitmap(*closed_icon);
+ HICON h_opened_icon = IconUtil::CreateHICONFromSkBitmap(*opened_icon);
+ ImageList_AddIcon(image_list, h_closed_icon);
+ ImageList_AddIcon(image_list, h_opened_icon);
+ DestroyIcon(h_closed_icon);
+ DestroyIcon(h_opened_icon);
+ }
+ return image_list;
+}
+
+TreeView::TreeView()
+ : tree_view_(NULL),
+ model_(NULL),
+ editable_(true),
+ next_id_(0),
+ controller_(NULL),
+ editing_node_(NULL),
+ root_shown_(true),
+ process_enter_(false),
+ show_context_menu_only_when_node_selected_(true),
+ select_on_right_mouse_down_(true),
+ wrapper_(this),
+ original_handler_(NULL) {
+}
+
+TreeView::~TreeView() {
+ if (model_)
+ model_->SetObserver(NULL);
+ // Both param_to_details_map_ and node_to_details_map_ have the same value,
+ // as such only need to delete from one.
+ STLDeleteContainerPairSecondPointers(id_to_details_map_.begin(),
+ id_to_details_map_.end());
+}
+
+void TreeView::SetModel(TreeModel* model) {
+ if (model == model_)
+ return;
+ if(model_ && tree_view_)
+ DeleteRootItems();
+ if (model_)
+ model_->SetObserver(NULL);
+ model_ = model;
+ if (tree_view_ && model_) {
+ CreateRootItems();
+ model_->SetObserver(this);
+ }
+}
+
+// Sets whether the user can edit the nodes. The default is true.
+void TreeView::SetEditable(bool editable) {
+ if (editable == editable_)
+ return;
+ editable_ = editable;
+ if (!tree_view_)
+ return;
+ LONG_PTR style = GetWindowLongPtr(tree_view_, GWL_STYLE);
+ style &= ~TVS_EDITLABELS;
+ SetWindowLongPtr(tree_view_, GWL_STYLE, style);
+}
+
+void TreeView::StartEditing(TreeModelNode* node) {
+ DCHECK(node && tree_view_);
+ // Cancel the current edit.
+ CancelEdit();
+ // Make sure all ancestors are expanded.
+ if (model_->GetParent(node))
+ Expand(model_->GetParent(node));
+ const NodeDetails* details = GetNodeDetails(node);
+ // Tree needs focus for editing to work.
+ SetFocus(tree_view_);
+ // Select the node, else if the user commits the edit the selection reverts.
+ SetSelectedNode(node);
+ TreeView_EditLabel(tree_view_, details->tree_item);
+}
+
+void TreeView::CancelEdit() {
+ DCHECK(tree_view_);
+ TreeView_EndEditLabelNow(tree_view_, TRUE);
+}
+
+void TreeView::CommitEdit() {
+ DCHECK(tree_view_);
+ TreeView_EndEditLabelNow(tree_view_, FALSE);
+}
+
+TreeModelNode* TreeView::GetEditingNode() {
+ // I couldn't find a way to dynamically query for this, so it is cached.
+ return editing_node_;
+}
+
+void TreeView::SetSelectedNode(TreeModelNode* node) {
+ DCHECK(tree_view_);
+ if (!node) {
+ TreeView_SelectItem(tree_view_, NULL);
+ return;
+ }
+ if (node != model_->GetRoot())
+ Expand(model_->GetParent(node));
+ if (!root_shown_ && node == model_->GetRoot()) {
+ // If the root isn't shown, we can't select it, clear out the selection
+ // instead.
+ TreeView_SelectItem(tree_view_, NULL);
+ } else {
+ // Select the node and make sure it is visible.
+ TreeView_SelectItem(tree_view_, GetNodeDetails(node)->tree_item);
+ }
+}
+
+TreeModelNode* TreeView::GetSelectedNode() {
+ if (!tree_view_)
+ return NULL;
+ HTREEITEM selected_item = TreeView_GetSelection(tree_view_);
+ if (!selected_item)
+ return NULL;
+ NodeDetails* details = GetNodeDetailsByTreeItem(selected_item);
+ DCHECK(details);
+ return details->node;
+}
+
+void TreeView::Expand(TreeModelNode* node) {
+ DCHECK(model_ && node);
+ if (!root_shown_ && model_->GetRoot() == node) {
+ // Can only expand the root if it is showing.
+ return;
+ }
+ TreeModelNode* parent = model_->GetParent(node);
+ if (parent) {
+ // Make sure all the parents are expanded.
+ Expand(parent);
+ }
+ // And expand this item.
+ TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND);
+}
+
+void TreeView::ExpandAll() {
+ DCHECK(model_);
+ ExpandAll(model_->GetRoot());
+}
+
+void TreeView::SetRootShown(bool root_shown) {
+ if (root_shown_ == root_shown)
+ return;
+ root_shown_ = root_shown;
+ if (!model_)
+ return;
+ // Repopulate the tree.
+ DeleteRootItems();
+ CreateRootItems();
+}
+
+void TreeView::TreeNodesAdded(TreeModel* model,
+ TreeModelNode* parent,
+ int start,
+ int count) {
+ DCHECK(parent && start >= 0 && count > 0);
+ if (node_to_details_map_.find(parent) == node_to_details_map_.end()) {
+ // User hasn't navigated to this entry yet. Ignore the change.
+ return;
+ }
+ HTREEITEM parent_tree_item = NULL;
+ if (root_shown_ || parent != model_->GetRoot()) {
+ const NodeDetails* details = GetNodeDetails(parent);
+ if (!details->loaded_children) {
+ if (count == model_->GetChildCount(parent)) {
+ // Reset the treeviews child count. This triggers the treeview to call
+ // us back.
+ TV_ITEM tv_item = {0};
+ tv_item.mask = TVIF_CHILDREN;
+ tv_item.cChildren = count;
+ tv_item.hItem = details->tree_item;
+ TreeView_SetItem(tree_view_, &tv_item);
+ }
+
+ // Ignore the change, we haven't actually created entries in the tree
+ // for the children.
+ return;
+ }
+ parent_tree_item = details->tree_item;
+ }
+
+ // The user has expanded this node, add the items to it.
+ for (int i = 0; i < count; ++i) {
+ if (i == 0 && start == 0) {
+ CreateItem(parent_tree_item, TVI_FIRST, model_->GetChild(parent, 0));
+ } else {
+ TreeModelNode* previous_sibling = model_->GetChild(parent, i + start - 1);
+ CreateItem(parent_tree_item,
+ GetNodeDetails(previous_sibling)->tree_item,
+ model_->GetChild(parent, i + start));
+ }
+ }
+}
+
+void TreeView::TreeNodesRemoved(TreeModel* model,
+ TreeModelNode* parent,
+ int start,
+ int count) {
+ DCHECK(parent && start >= 0 && count > 0);
+ if (node_to_details_map_.find(parent) == node_to_details_map_.end()) {
+ // User hasn't navigated to this entry yet. Ignore the change.
+ return;
+ }
+ HTREEITEM parent_tree_item = NULL;
+ if (!root_shown_ || parent != model_->GetRoot()) {
+ const NodeDetails* details = GetNodeDetails(parent);
+ if (!details->loaded_children) {
+ // Ignore the change, we haven't actually created entries in the tree
+ // for the children.
+ return;
+ }
+ parent_tree_item = details->tree_item;
+ } else {
+ parent_tree_item = TreeView_GetRoot(tree_view_);
+ }
+ // Find the last item. Windows doesn't offer a convenient way to get the
+ // TREEITEM at a particular index, so we iterate.
+ HTREEITEM tree_item = TreeView_GetChild(tree_view_, parent_tree_item);
+ for (int i = 0; i < (start + count - 1); ++i) {
+ tree_item = TreeView_GetNextSibling(tree_view_, tree_item);
+ }
+ // NOTE: the direction doesn't matter here. I've made it backwards to
+ // reinforce we're deleting from the end forward.
+ for (int i = count - 1; i >= 0; --i) {
+ HTREEITEM previous = (start + i) > 0 ?
+ TreeView_GetPrevSibling(tree_view_, tree_item) : NULL;
+ RecursivelyDelete(GetNodeDetailsByTreeItem(tree_item));
+ tree_item = previous;
+ }
+}
+
+void TreeView::TreeNodeChanged(TreeModel* model, TreeModelNode* node) {
+ if (node_to_details_map_.find(node) == node_to_details_map_.end()) {
+ // User hasn't navigated to this entry yet. Ignore the change.
+ return;
+ }
+ const NodeDetails* details = GetNodeDetails(node);
+ TV_ITEM tv_item = {0};
+ tv_item.mask = TVIF_TEXT;
+ tv_item.hItem = details->tree_item;
+ tv_item.pszText = LPSTR_TEXTCALLBACK;
+ TreeView_SetItem(tree_view_, &tv_item);
+}
+
+HWND TreeView::CreateNativeControl(HWND parent_container) {
+ int style = WS_CHILD | TVS_DISABLEDRAGDROP | TVS_HASBUTTONS |
+ TVS_HASLINES | TVS_SHOWSELALWAYS;
+ if (editable_)
+ style |= TVS_EDITLABELS;
+ tree_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(),
+ WC_TREEVIEW,
+ L"",
+ style,
+ 0, 0, GetWidth(), GetHeight(),
+ parent_container, NULL, NULL, NULL);
+ SetWindowLongPtr(tree_view_, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(&wrapper_));
+ original_handler_ = win_util::SetWindowProc(tree_view_,
+ &TreeWndProc);
+ // Tree-View doesn't render icons by default. Use an image list that is
+ // populated with icons from the shell.
+ if (!tree_image_list_)
+ tree_image_list_ = CreateDefaultImageList(UILayoutIsRightToLeft());
+ if (tree_image_list_)
+ TreeView_SetImageList(tree_view_, tree_image_list_, TVSIL_NORMAL);
+
+ if (model_) {
+ CreateRootItems();
+ model_->SetObserver(this);
+ }
+
+ // Bug 964884: detach the IME attached to this window.
+ // We should attach IMEs only when we need to input CJK strings.
+ ::ImmAssociateContextEx(tree_view_, NULL, 0);
+ return tree_view_;
+}
+
+LRESULT TreeView::OnNotify(int w_param, LPNMHDR l_param) {
+ switch (l_param->code) {
+ case TVN_GETDISPINFO: {
+ // Windows is requesting more information about an item.
+ // WARNING: At the time this is called the tree_item of the NodeDetails
+ // in the maps is NULL.
+ DCHECK(model_);
+ NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
+ const NodeDetails* details =
+ GetNodeDetailsByID(static_cast<int>(info->item.lParam));
+ if (info->item.mask & TVIF_CHILDREN)
+ info->item.cChildren = model_->GetChildCount(details->node);
+ if (info->item.mask & TVIF_TEXT) {
+ std::wstring text = details->node->GetTitle();
+ DCHECK(info->item.cchTextMax);
+
+ // Adjust the string direction if such adjustment is required.
+ std::wstring localized_text;
+ if (l10n_util::AdjustStringForLocaleDirection(text, &localized_text))
+ text.swap(localized_text);
+
+ wcsncpy_s(info->item.pszText, info->item.cchTextMax, text.c_str(),
+ _TRUNCATE);
+ }
+ // Instructs windows to cache the values for this node.
+ info->item.mask |= TVIF_DI_SETITEM;
+ // Return value ignored.
+ return 0;
+ }
+
+ case TVN_ITEMEXPANDING: {
+ // Notification that a node is expanding. If we haven't populated the
+ // tree view with the contents of the model, we do it here.
+ DCHECK(model_);
+ NMTREEVIEW* info = reinterpret_cast<NMTREEVIEW*>(l_param);
+ NodeDetails* details =
+ GetNodeDetailsByID(static_cast<int>(info->itemNew.lParam));
+ if (!details->loaded_children) {
+ details->loaded_children = true;
+ for (int i = 0; i < model_->GetChildCount(details->node); ++i)
+ CreateItem(details->tree_item, TVI_LAST,
+ model_->GetChild(details->node, i));
+ }
+ // Return FALSE to allow the item to be expanded.
+ return FALSE;
+ }
+
+ case TVN_SELCHANGED:
+ if (controller_)
+ controller_->OnTreeViewSelectionChanged(this);
+ break;
+
+ case TVN_BEGINLABELEDIT: {
+ NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
+ NodeDetails* details =
+ GetNodeDetailsByID(static_cast<int>(info->item.lParam));
+ // Return FALSE to allow editing.
+ if (!controller_ || controller_->CanEdit(this, details->node)) {
+ editing_node_ = details->node;
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ case TVN_ENDLABELEDIT: {
+ NMTVDISPINFO* info = reinterpret_cast<NMTVDISPINFO*>(l_param);
+ if (info->item.pszText) {
+ // User accepted edit.
+ NodeDetails* details =
+ GetNodeDetailsByID(static_cast<int>(info->item.lParam));
+ model_->SetTitle(details->node, info->item.pszText);
+ editing_node_ = NULL;
+ // Return FALSE so that the tree item doesn't change its text (if the
+ // model changed the value, it should have sent out notification which
+ // will have updated the value).
+ return FALSE;
+ }
+ editing_node_ = NULL;
+ // Return value ignored.
+ return 0;
+ }
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+bool TreeView::OnKeyDown(int virtual_key_code) {
+ if (virtual_key_code == VK_F2) {
+ if (!GetEditingNode()) {
+ TreeModelNode* selected_node = GetSelectedNode();
+ if (selected_node)
+ StartEditing(selected_node);
+ }
+ return true;
+ } else if (virtual_key_code == VK_RETURN && !process_enter_) {
+ ViewContainer* vc = GetViewContainer();
+ DCHECK(vc);
+ FocusManager* fm = FocusManager::GetFocusManager(vc->GetHWND());
+ DCHECK(fm);
+ Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code),
+ win_util::IsShiftPressed(),
+ win_util::IsCtrlPressed(),
+ win_util::IsAltPressed()));
+ fm->ProcessAccelerator(accelerator, true);
+ return true;
+ }
+ return false;
+}
+
+void TreeView::OnContextMenu(const CPoint& location) {
+ if (GetContextMenuController()) {
+ if (location.x == -1 && location.y == -1) {
+ // Indicates the user pressed the context menu key.
+ bool valid_loc = false;
+ int x, y;
+ if (GetSelectedNode()) {
+ RECT bounds;
+ if (TreeView_GetItemRect(tree_view_,
+ GetNodeDetails(GetSelectedNode())->tree_item,
+ &bounds, TRUE)) {
+ x = bounds.left;
+ y = bounds.top + (bounds.bottom - bounds.top) / 2;
+ valid_loc = true;
+ }
+ } else if (show_context_menu_only_when_node_selected_) {
+ return;
+ }
+ if (!valid_loc) {
+ x = GetWidth() / 2;
+ y = GetHeight() / 2;
+ }
+ CPoint screen_loc(x, y);
+ ConvertPointToScreen(this, &screen_loc);
+ GetContextMenuController()->ShowContextMenu(this, screen_loc.x,
+ screen_loc.y, false);
+ } else if (!show_context_menu_only_when_node_selected_) {
+ GetContextMenuController()->ShowContextMenu(this, location.x, location.y,
+ true);
+ } else if (GetSelectedNode()) {
+ // Make sure the mouse is over the selected node.
+ TVHITTESTINFO hit_info;
+ CPoint local_loc(location);
+ ConvertPointToView(NULL, this, &local_loc);
+ hit_info.pt.x = local_loc.x;
+ hit_info.pt.y = local_loc.y;
+ HTREEITEM hit_item = TreeView_HitTest(tree_view_, &hit_info);
+ if (hit_item &&
+ GetNodeDetails(GetSelectedNode())->tree_item == hit_item &&
+ (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT |
+ TVHT_ONITEMINDENT)) != 0) {
+ GetContextMenuController()->ShowContextMenu(this, location.x,
+ location.y, true);
+ }
+ }
+ }
+}
+
+void TreeView::ExpandAll(TreeModelNode* node) {
+ DCHECK(node);
+ // Expand the node.
+ if (node != model_->GetRoot() || root_shown_)
+ TreeView_Expand(tree_view_, GetNodeDetails(node)->tree_item, TVE_EXPAND);
+ // And recursively expand all the children.
+ for (int i = model_->GetChildCount(node) - 1; i >= 0; --i) {
+ TreeModelNode* child = model_->GetChild(node, i);
+ ExpandAll(child);
+ }
+}
+
+void TreeView::DeleteRootItems() {
+ HTREEITEM root = TreeView_GetRoot(tree_view_);
+ if (root) {
+ if (root_shown_) {
+ RecursivelyDelete(GetNodeDetailsByTreeItem(root));
+ } else {
+ HTREEITEM node;
+ while ((node = TreeView_GetChild(tree_view_, root))) {
+ RecursivelyDelete(GetNodeDetailsByTreeItem(node));
+ }
+ }
+ }
+}
+
+void TreeView::CreateRootItems() {
+ DCHECK(model_);
+ TreeModelNode* root = model_->GetRoot();
+ if (root_shown_) {
+ CreateItem(NULL, TVI_LAST, root);
+ } else {
+ for (int i = 0; i < model_->GetChildCount(root); ++i)
+ CreateItem(NULL, TVI_LAST, model_->GetChild(root, i));
+ }
+}
+
+void TreeView::CreateItem(HTREEITEM parent_item,
+ HTREEITEM after,
+ TreeModelNode* node) {
+ DCHECK(node);
+ TVINSERTSTRUCT insert_struct = {0};
+ insert_struct.hParent = parent_item;
+ insert_struct.hInsertAfter = after;
+ insert_struct.itemex.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_TEXT |
+ TVIF_SELECTEDIMAGE;
+ // Call us back for the text.
+ insert_struct.itemex.pszText = LPSTR_TEXTCALLBACK;
+ // And the number of children.
+ insert_struct.itemex.cChildren = I_CHILDRENCALLBACK;
+ // Index in the image list for the image when the node is selected.
+ insert_struct.itemex.iSelectedImage = 1;
+ int node_id = next_id_++;
+ insert_struct.itemex.lParam = node_id;
+
+ // Invoking TreeView_InsertItem triggers OnNotify to be called. As such,
+ // we set the map entries before adding the item.
+ NodeDetails* node_details = new NodeDetails(node_id, node);
+
+ node_to_details_map_[node] = node_details;
+ id_to_details_map_[node_id] = node_details;
+
+ node_details->tree_item = TreeView_InsertItem(tree_view_, &insert_struct);
+}
+
+void TreeView::RecursivelyDelete(NodeDetails* node) {
+ DCHECK(node);
+ HTREEITEM item = node->tree_item;
+ DCHECK(item);
+
+ // Recurse through children.
+ for (HTREEITEM child = TreeView_GetChild(tree_view_, item);
+ child ; child = TreeView_GetNextSibling(tree_view_, child)) {
+ RecursivelyDelete(GetNodeDetailsByTreeItem(child));
+ }
+
+ TreeView_DeleteItem(tree_view_, item);
+
+ // finally, it is safe to delete the data for this node.
+ id_to_details_map_.erase(node->id);
+ node_to_details_map_.erase(node->node);
+ delete node;
+}
+
+TreeView::NodeDetails* TreeView::GetNodeDetailsByTreeItem(HTREEITEM tree_item) {
+ DCHECK(tree_view_ && tree_item);
+ TV_ITEM tv_item = {0};
+ tv_item.hItem = tree_item;
+ tv_item.mask = TVIF_PARAM;
+ if (TreeView_GetItem(tree_view_, &tv_item))
+ return GetNodeDetailsByID(static_cast<int>(tv_item.lParam));
+ return NULL;
+}
+
+LRESULT CALLBACK TreeView::TreeWndProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
+ TreeViewWrapper* wrapper = reinterpret_cast<TreeViewWrapper*>(
+ GetWindowLongPtr(window, GWLP_USERDATA));
+ DCHECK(wrapper);
+ TreeView* tree = wrapper->tree_view;
+ if (message == WM_RBUTTONDOWN && tree->select_on_right_mouse_down_) {
+ TVHITTESTINFO hit_info;
+ hit_info.pt.x = GET_X_LPARAM(l_param);
+ hit_info.pt.y = GET_Y_LPARAM(l_param);
+ HTREEITEM hit_item = TreeView_HitTest(window, &hit_info);
+ if (hit_item && (hit_info.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT |
+ TVHT_ONITEMINDENT)) != 0)
+ TreeView_SelectItem(tree->tree_view_, hit_item);
+ // Fall through and let the default handler process as well.
+ }
+ WNDPROC handler = tree->original_handler_;
+ DCHECK(handler);
+ return CallWindowProc(handler, window, message, w_param, l_param);
+}
+
+} // namespace ChromeViews