diff --git a/src/NppJsonViewer/JsonHandler.h b/src/NppJsonViewer/JsonHandler.h index ef3b448..f7988a9 100644 --- a/src/NppJsonViewer/JsonHandler.h +++ b/src/NppJsonViewer/JsonHandler.h @@ -10,6 +10,7 @@ #include #include "Define.h" +#include "TrackingStream.h" namespace rj = rapidjson; @@ -42,7 +43,7 @@ class JsonHandler auto ValidateJson(const std::string& jsonText) -> const Result; template - auto ParseJson(const std::string& jsonText, rj::StringBuffer& sb, Handler& handler) -> const Result; + auto ParseJson(const std::string& jsonText, rj::StringBuffer& sb, Handler& handler, TrackingStreamSharedPtr pTS = nullptr) -> const Result; private: void SortJsonObject(rj::Value& jsonObject, rj::Document::AllocatorType& allocator) const; @@ -51,13 +52,18 @@ class JsonHandler }; template -inline auto JsonHandler::ParseJson(const std::string& jsonText, rj::StringBuffer& sb, Handler& handler) -> const Result +inline auto JsonHandler::ParseJson(const std::string& jsonText, rj::StringBuffer& sb, Handler& handler, TrackingStreamSharedPtr pTS) -> const Result { Result retVal {}; - bool success = false; - rj::Reader reader; - rj::StringStream ss(jsonText.c_str()); + bool success = false; + rj::Reader reader; + + std::shared_ptr pSS = nullptr; + if (!pTS) + { + pSS = std::make_shared(jsonText.c_str()); + } // TODO: Find some better way constexpr auto flgBase_comment = flgBase | rj::kParseCommentsFlag; @@ -66,22 +72,22 @@ inline auto JsonHandler::ParseJson(const std::string& jsonText, rj::StringBuffer if (m_parseOptions.bIgnoreComment && m_parseOptions.bIgnoreTrailingComma) { - success = reader.Parse(ss, handler) && sb.GetString(); + success = pTS ? reader.Parse(*pTS, handler) && sb.GetString() : reader.Parse(*pSS, handler) && sb.GetString(); } else if (!m_parseOptions.bIgnoreComment && m_parseOptions.bIgnoreTrailingComma) { - success = reader.Parse(ss, handler) && sb.GetString(); + success = pTS ? reader.Parse(*pTS, handler) && sb.GetString() : reader.Parse(*pSS, handler) && sb.GetString(); } else if (m_parseOptions.bIgnoreComment && !m_parseOptions.bIgnoreTrailingComma) { - success = reader.Parse(ss, handler) && sb.GetString(); + success = pTS ? reader.Parse(*pTS, handler) && sb.GetString() : reader.Parse(*pSS, handler) && sb.GetString(); } else if (!m_parseOptions.bIgnoreComment && !m_parseOptions.bIgnoreTrailingComma) { - success = reader.Parse(ss, handler) && sb.GetString(); + success = pTS ? reader.Parse(*pTS, handler) && sb.GetString() : reader.Parse(*pSS, handler) && sb.GetString(); } if (success) diff --git a/src/NppJsonViewer/JsonNode.h b/src/NppJsonViewer/JsonNode.h index a1e5d02..58f3c95 100644 --- a/src/NppJsonViewer/JsonNode.h +++ b/src/NppJsonViewer/JsonNode.h @@ -11,9 +11,32 @@ enum class JsonNodeType : short OBJECT, }; +struct Position +{ + size_t nLine {}; + size_t nColumn {}; + + void clear() + { + nLine = nColumn = 0; + } +}; + +struct JsonKey +{ + Position pos {}; + std::string strKey; + + void clear() + { + pos.clear(); + strKey.clear(); + } +}; + struct JsonNode { - std::string key; + JsonKey key; std::string value; JsonNodeType type = JsonNodeType::UNKNOWN; }; diff --git a/src/NppJsonViewer/JsonViewDlg.cpp b/src/NppJsonViewer/JsonViewDlg.cpp index 77d0627..3387269 100644 --- a/src/NppJsonViewer/JsonViewDlg.cpp +++ b/src/NppJsonViewer/JsonViewDlg.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "JsonViewDlg.h" #include "Define.h" #include "Utility.h" @@ -5,8 +8,7 @@ #include "RapidJsonHandler.h" #include "ScintillaEditor.h" #include "Profile.h" -#include -#include + constexpr int FILENAME_LEN_IN_TITLE = 16; @@ -100,6 +102,8 @@ void JsonViewDlg::FormatJson() ReportError(res); } + + ReDrawJsonTree(); } void JsonViewDlg::CompressJson() @@ -130,6 +134,8 @@ void JsonViewDlg::CompressJson() ReportError(res); } + + ReDrawJsonTree(); } void JsonViewDlg::SortJsonByKey() @@ -162,6 +168,8 @@ void JsonViewDlg::SortJsonByKey() ReportError(res); } + + ReDrawJsonTree(); } bool JsonViewDlg::CheckForTokenUndefined(eMethod method, std::string selectedText, Result& res, HTREEITEM tree_root) @@ -171,14 +179,8 @@ bool JsonViewDlg::CheckForTokenUndefined(eMethod method, std::string selectedTex if (m_pSetting->parseOptions.bReplaceUndefined) { auto text = selectedText.substr(res.error_pos, 9); - std::transform( - text.begin(), - text.end(), - text.begin(), - [](unsigned char c) - { - return (unsigned char)std::tolower(c); - }); + StringHelper::ToLower(text); + if (text == "undefined") { try @@ -217,7 +219,7 @@ bool JsonViewDlg::CheckForTokenUndefined(eMethod method, std::string selectedTex else { m_pEditor->ReplaceSelection(text); - m_pEditor->MakeSelection(m_pEditor->GetSelectionStart(), static_cast(text.length())); + m_pEditor->MakeSelection(m_pEditor->GetSelectionStart(), text.length()); m_pEditor->RefreshSelectionPos(); } } @@ -326,6 +328,8 @@ void JsonViewDlg::ValidateJson() ReportError(res); } + + DrawJsonTree(); } void JsonViewDlg::DrawJsonTree() @@ -379,6 +383,16 @@ void JsonViewDlg::DrawJsonTree() EnableControls(ctrls, true); } +void JsonViewDlg::ReDrawJsonTree(bool bForce) +{ + const bool bIsVisible = isCreated() && isVisible(); + const bool bReDraw = bForce || bIsVisible; + if (bReDraw) + { + DrawJsonTree(); + } +} + void JsonViewDlg::HighlightAsJson(bool bForcefully) const { bool setJsonLang = bForcefully || m_pSetting->bUseJsonHighlight; @@ -390,10 +404,11 @@ auto JsonViewDlg::PopulateTreeUsingSax(HTREEITEM tree_root, const std::string& j { std::optional retVal = std::nullopt; - RapidJsonHandler handler(this, tree_root); + auto pTS = std::make_shared(jsonText); + RapidJsonHandler handler(this, tree_root, pTS); rapidjson::StringBuffer sb; - Result res = JsonHandler(m_pSetting->parseOptions).ParseJson(jsonText, sb, handler); + Result res = JsonHandler(m_pSetting->parseOptions).ParseJson(jsonText, sb, handler, pTS); if (!res.success) { if (CheckForTokenUndefined(JsonViewDlg::eMethod::ParseJson, jsonText, res, tree_root)) @@ -429,6 +444,13 @@ HTREEITEM JsonViewDlg::InsertToTree(HTREEITEM parent, const std::string& text) return m_hTreeView->InsertNode(wText, NULL, parent); } +HTREEITEM JsonViewDlg::InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) +{ + auto wText = StringHelper::ToWstring(text, CP_UTF8); + auto lparam = new Position(pos); + return m_hTreeView->InsertNode(wText, reinterpret_cast(lparam), parent); +} + void JsonViewDlg::AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) { if (!node) @@ -450,6 +472,16 @@ void JsonViewDlg::UpdateNodePath(HTREEITEM htiNode) CUtility::SetEditCtrlText(::GetDlgItem(_hSelf, IDC_EDT_NODEPATH), nodePath); } +void JsonViewDlg::GoToLine(size_t nLineToGo) +{ + m_pEditor->GoToLine(nLineToGo); +} + +void JsonViewDlg::GoToPosition(size_t nLineToGo, size_t nPos) +{ + m_pEditor->GoToPosition(nLineToGo, nPos); +} + void JsonViewDlg::SearchInTree() { std::wstring itemToSearch = CUtility::GetEditCtrlText(::GetDlgItem(_hSelf, IDC_EDT_SEARCH)); @@ -878,6 +910,24 @@ void JsonViewDlg::HandleTreeEvents(LPARAM lParam) if (hItem && (pnmtv->action == TVC_BYMOUSE || pnmtv->action == TVC_BYKEYBOARD)) { UpdateNodePath(hItem); + + auto pPosition = m_hTreeView->GetNodePosition(hItem); + if (pPosition != nullptr) + { + GoToLine(pPosition->nLine); + } + } + } + break; + + case NM_DBLCLK: + { + HTREEITEM hItem = m_hTreeView->GetSelection(); + + auto pPosition = m_hTreeView->GetNodePosition(hItem); + if (pPosition != nullptr) + { + GoToPosition(pPosition->nLine, pPosition->nColumn); } } break; diff --git a/src/NppJsonViewer/JsonViewDlg.h b/src/NppJsonViewer/JsonViewDlg.h index 598ecf4..d3f745c 100644 --- a/src/NppJsonViewer/JsonViewDlg.h +++ b/src/NppJsonViewer/JsonViewDlg.h @@ -11,6 +11,7 @@ #include "TreeViewCtrl.h" #include "ScintillaEditor.h" #include "JsonHandler.h" +#include "JsonNode.h" class JsonViewDlg : public DockingDlgInterface @@ -44,16 +45,20 @@ class JsonViewDlg : public DockingDlgInterface void UpdateTitle(); HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text); + HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos); void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray); private: void DrawJsonTree(); + void ReDrawJsonTree(bool bForce = false); void HighlightAsJson(bool bForcefully = false) const; auto PopulateTreeUsingSax(HTREEITEM tree_root, const std::string& jsonText) -> std::optional; void ValidateJson(); void UpdateNodePath(HTREEITEM htiNode); + void GoToLine(size_t nLineToGo); + void GoToPosition(size_t nLineToGo, size_t nPos); void SearchInTree(); diff --git a/src/NppJsonViewer/NPPJSONViewer.vcxproj b/src/NppJsonViewer/NPPJSONViewer.vcxproj index 1c485a7..9665dfa 100644 --- a/src/NppJsonViewer/NPPJSONViewer.vcxproj +++ b/src/NppJsonViewer/NPPJSONViewer.vcxproj @@ -222,6 +222,7 @@ + diff --git a/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters b/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters index 130d490..f7cdb05 100644 --- a/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters +++ b/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters @@ -128,6 +128,9 @@ ThirdParty\npp + + Header Files + diff --git a/src/NppJsonViewer/RapidJsonHandler.cpp b/src/NppJsonViewer/RapidJsonHandler.cpp index ae2f370..6265a68 100644 --- a/src/NppJsonViewer/RapidJsonHandler.cpp +++ b/src/NppJsonViewer/RapidJsonHandler.cpp @@ -81,7 +81,9 @@ bool RapidJsonHandler::String(const Ch* str, unsigned /*length*/, bool /*copy*/) bool RapidJsonHandler::Key(const Ch* str, unsigned /*length*/, bool /*copy*/) { - m_strLastKey = str; + m_jsonLastKey.strKey = str; + m_jsonLastKey.pos.nLine = m_pTS->getLine(); + m_jsonLastKey.pos.nColumn = m_pTS->getColumn(); return true; } @@ -101,13 +103,13 @@ bool RapidJsonHandler::StartObject() parent = m_NodeStack.top(); } - if (!m_strLastKey.empty() || parent->node.type == JsonNodeType::ARRAY) + if (!m_jsonLastKey.strKey.empty() || parent->node.type == JsonNodeType::ARRAY) { HTREEITEM newNode = nullptr; if (parent->node.type != JsonNodeType::ARRAY) { - newNode = m_dlg->InsertToTree(parent->subRoot, m_strLastKey); - m_strLastKey.clear(); + newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); + m_jsonLastKey.clear(); } else { @@ -150,13 +152,13 @@ bool RapidJsonHandler::StartArray() parent = m_NodeStack.top(); } - if (!m_strLastKey.empty() || parent->node.type == JsonNodeType::ARRAY) + if (!m_jsonLastKey.strKey.empty() || parent->node.type == JsonNodeType::ARRAY) { HTREEITEM newNode; if (parent->node.type != JsonNodeType::ARRAY) { - newNode = m_dlg->InsertToTree(parent->subRoot, m_strLastKey); - m_strLastKey.clear(); + newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); + m_jsonLastKey.clear(); } else { @@ -188,22 +190,22 @@ void RapidJsonHandler::InsertToTree(TreeNode* node, const char* const str, bool if (node->node.type != JsonNodeType::ARRAY) { - node->node.key = m_strLastKey; + node->node.key = m_jsonLastKey; node->node.value = str; - m_strLastKey.clear(); + m_jsonLastKey.clear(); } else { - node->node.key = "[" + std::to_string(node->counter) + "]"; - node->node.value = str; + node->node.key.strKey = "[" + std::to_string(node->counter) + "]"; + node->node.value = str; node->counter++; } // Insert item to tree if (bQuote) - m_dlg->InsertToTree(node->subRoot, node->node.key + " : \"" + node->node.value + "\""); + m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : \"" + node->node.value + "\"", node->node.key.pos); else - m_dlg->InsertToTree(node->subRoot, node->node.key + " : " + node->node.value); + m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : " + node->node.value, node->node.key.pos); } void RapidJsonHandler::AppendNodeCount(unsigned elementCount, bool bArray) diff --git a/src/NppJsonViewer/RapidJsonHandler.h b/src/NppJsonViewer/RapidJsonHandler.h index 16d4f24..a912276 100644 --- a/src/NppJsonViewer/RapidJsonHandler.h +++ b/src/NppJsonViewer/RapidJsonHandler.h @@ -8,7 +8,7 @@ #include #include "JsonNode.h" - +#include "TrackingStream.h" class JsonViewDlg; @@ -19,17 +19,20 @@ struct TreeNode int counter {}; }; + class RapidJsonHandler : public rapidjson::BaseReaderHandler, RapidJsonHandler> { - std::string m_strLastKey; + JsonKey m_jsonLastKey {}; std::stack m_NodeStack; - JsonViewDlg* m_dlg = nullptr; - HTREEITEM m_treeRoot = nullptr; + TrackingStreamSharedPtr m_pTS; + JsonViewDlg* m_dlg = nullptr; + HTREEITEM m_treeRoot = nullptr; public: - RapidJsonHandler(JsonViewDlg* dlg, HTREEITEM treeRoot) - : m_dlg(dlg) + RapidJsonHandler(JsonViewDlg* dlg, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr) + : m_pTS(pTS ? pTS->GetShared() : nullptr) + , m_dlg(dlg) , m_treeRoot(treeRoot) { } diff --git a/src/NppJsonViewer/ScintillaEditor.cpp b/src/NppJsonViewer/ScintillaEditor.cpp index c0c5d25..fda6652 100644 --- a/src/NppJsonViewer/ScintillaEditor.cpp +++ b/src/NppJsonViewer/ScintillaEditor.cpp @@ -69,6 +69,9 @@ auto ScintillaEditor::GetCurrentFileName() const -> std::wstring void ScintillaEditor::ReplaceSelection(const std::string& text) const { ::SendMessage(m_hScintilla, SCI_REPLACESEL, 0, reinterpret_cast(text.c_str())); + + // Restore the selection + MakeSelection(m_nStartPos, m_nStartPos + text.length()); } void ScintillaEditor::MakeSelection(size_t start, size_t end) const @@ -97,4 +100,18 @@ void ScintillaEditor::RefreshSelectionPos() if (m_nEndPos < m_nStartPos) std::swap(m_nStartPos, m_nEndPos); + + m_nStartLine = ::SendMessage(m_hScintilla, SCI_LINEFROMPOSITION, m_nStartPos, 0); +} + +void ScintillaEditor::GoToLine(size_t nLineToGo) const +{ + ::SendMessage(m_hScintilla, SCI_GOTOLINE, m_nStartLine + nLineToGo, 0); +} + +void ScintillaEditor::GoToPosition(size_t nLineToGo, size_t nColumnIndex) const +{ + size_t lineStartPos = SendMessage(m_hScintilla, SCI_POSITIONFROMLINE, m_nStartLine + nLineToGo, 0); + size_t targetPos = lineStartPos + nColumnIndex; + ::SendMessage(m_hScintilla, SCI_GOTOPOS, targetPos, 0); } diff --git a/src/NppJsonViewer/ScintillaEditor.h b/src/NppJsonViewer/ScintillaEditor.h index 6b3b675..a7747d3 100644 --- a/src/NppJsonViewer/ScintillaEditor.h +++ b/src/NppJsonViewer/ScintillaEditor.h @@ -29,25 +29,33 @@ class ScintillaEditor void ReplaceSelection(const std::string& text) const; - void MakeSelection(size_t start, size_t end) const; - auto GetSelectionStart() const -> size_t + void MakeSelection(size_t start, size_t end) const; + inline auto GetSelectionStart() const -> size_t { return m_nStartPos; } - auto GetSelectionEnd() const -> size_t + inline auto GetSelectionEnd() const -> size_t { return m_nEndPos; } + inline auto GetSelectionStartLine() const -> size_t + { + return m_nStartLine; + } auto GetEOL() const -> unsigned; auto GetIndent() const -> std::tuple; void RefreshSelectionPos(); + void GoToLine(size_t nLineToGo) const; + void GoToPosition(size_t nLineToGo, size_t nColumnIndex) const; + private: NppData m_NppData = {}; HWND m_hScintilla = nullptr; - size_t m_nStartPos = 0; - size_t m_nEndPos = 0; + size_t m_nStartLine = 0; + size_t m_nStartPos = 0; + size_t m_nEndPos = 0; }; diff --git a/src/NppJsonViewer/TrackingStream.h b/src/NppJsonViewer/TrackingStream.h new file mode 100644 index 0000000..51a84bb --- /dev/null +++ b/src/NppJsonViewer/TrackingStream.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +#include + + +class TrackingStream : public std::enable_shared_from_this +{ +private: + char m_chPrevChar {}; // Store previous character for handling column postion + size_t m_nLine {}; + size_t m_nColumn {}; + rapidjson::StringStream m_ss; + +public: + using Ch = char; // Define Ch to conform to RapidJSON's expectations + + TrackingStream(const std::string& jsonText) + : m_ss(jsonText.c_str()) + , m_nLine(0) + , m_nColumn(0) + , m_chPrevChar('\0') + { + } + + std::shared_ptr GetShared() + { + return shared_from_this(); + } + + inline size_t getLine() const + { + return m_nLine; + } + + inline size_t getColumn() const + { + return m_nColumn; + } + + // Read the next character and update line/column numbers + Ch Take() + { + Ch ch = m_ss.Take(); + if (ch == '\n') + { + ++m_nLine; + m_nColumn = 1; + } + else + { + ++m_nColumn; + } + m_chPrevChar = ch; + return ch; + } + + Ch Peek() const + { + return m_ss.Peek(); + } + + size_t Tell() const + { + return m_ss.Tell(); + } + + Ch* PutBegin() + { + return m_ss.PutBegin(); + } + + size_t PutEnd(Ch* pCh) + { + return m_ss.PutEnd(pCh); + } + + void Put(Ch ch) + { + m_ss.Put(ch); + } +}; + +using TrackingStreamSharedPtr = std::shared_ptr; diff --git a/src/NppJsonViewer/TreeViewCtrl.cpp b/src/NppJsonViewer/TreeViewCtrl.cpp index b98698b..844c685 100644 --- a/src/NppJsonViewer/TreeViewCtrl.cpp +++ b/src/NppJsonViewer/TreeViewCtrl.cpp @@ -1,7 +1,8 @@ +#include + #include "TreeViewCtrl.h" #include "Define.h" #include "resource.h" -#include void TreeViewCtrl::OnInit(HWND hParent) @@ -13,7 +14,8 @@ void TreeViewCtrl::OnInit(HWND hParent) auto TreeViewCtrl::InitTree() -> HTREEITEM { if (GetNodeCount() > 0) - TreeView_DeleteAllItems(m_hTree); + DeleteAllNodes(); + m_nMaxNodeTextLength = 0; return InsertNode(JSON_ROOT, -1, TVI_ROOT); @@ -199,6 +201,17 @@ auto TreeViewCtrl::GetNodeName(HTREEITEM hti, bool removeTrailingCount) const -> return retVal; } +auto TreeViewCtrl::GetNodePos(HTREEITEM hti) const -> LPARAM +{ + TVITEM tvItem {}; + tvItem.hItem = hti; + tvItem.mask = TVIF_PARAM; + + if (SendMessage(m_hTree, TVM_GETITEM, 0, reinterpret_cast(&tvItem))) + return tvItem.lParam; + return -1; +} + auto TreeViewCtrl::GetNodeKey(HTREEITEM hti) const -> std::wstring { std::wstring retVal = GetNodeName(hti, true); @@ -268,6 +281,20 @@ auto TreeViewCtrl::GetNodePath(HTREEITEM hti) const -> std::wstring return wstrJsonPath; } +auto TreeViewCtrl::GetNodePosition(HTREEITEM hti) const -> Position* +{ + Position* pPosition = nullptr; + if (hti != nullptr) + { + LPARAM nodePos = GetNodePos(hti); + if (nodePos != -1) + { + pPosition = reinterpret_cast(nodePos); + } + } + return pPosition; +} + HTREEITEM TreeViewCtrl::GetSelection() const { return TreeView_GetSelection(m_hTree); @@ -339,3 +366,35 @@ bool TreeViewCtrl::SetTVItem(TVITEM* tvi) const { return TreeView_SetItem(m_hTree, tvi) ? true : false; } + +void TreeViewCtrl::FreeNodeData(HTREEITEM hItem) +{ + if (hItem == nullptr) + return; + + Position* pNodeKeyPos = GetNodePosition(hItem); + if (pNodeKeyPos) + { + delete pNodeKeyPos; + pNodeKeyPos = nullptr; + } + + HTREEITEM hChild = TreeView_GetChild(m_hTree, hItem); + while (hChild != nullptr) + { + FreeNodeData(hChild); + hChild = TreeView_GetNextSibling(m_hTree, hChild); + } +} + +void TreeViewCtrl::DeleteAllNodes() +{ + HTREEITEM hRoot = GetRoot(); + + if (hRoot != nullptr) + { + FreeNodeData(hRoot); + } + + TreeView_DeleteAllItems(m_hTree); +} diff --git a/src/NppJsonViewer/TreeViewCtrl.h b/src/NppJsonViewer/TreeViewCtrl.h index 087b0df..73dfb01 100644 --- a/src/NppJsonViewer/TreeViewCtrl.h +++ b/src/NppJsonViewer/TreeViewCtrl.h @@ -1,7 +1,11 @@ #pragma once + +#include + #include #include -#include + +#include "JsonNode.h" class TreeViewCtrl { @@ -52,9 +56,11 @@ class TreeViewCtrl HTREEITEM NextItem(HTREEITEM htiCurrent, HTREEITEM htiNextRoot) const; auto GetNodeName(HTREEITEM hti, bool removeTrailingCount) const -> std::wstring; + auto GetNodePos(HTREEITEM hti) const -> LPARAM; auto GetNodeKey(HTREEITEM hti) const -> std::wstring; auto GetNodeValue(HTREEITEM hti) const -> std::wstring; auto GetNodePath(HTREEITEM hti) const -> std::wstring; + auto GetNodePosition(HTREEITEM hti) const -> Position*; private: void ExpandOrCollapse(HTREEITEM node, UINT_PTR code) const; @@ -63,4 +69,7 @@ class TreeViewCtrl bool GetTVItem(HTREEITEM hti, TVITEM* tvi) const; bool SetTVItem(TVITEM* tvi) const; + + void FreeNodeData(HTREEITEM hItem); + void DeleteAllNodes(); };