Skip to content

BUG-148 - Shared Files directory tree node stuck expanded after collapse and cannot re-expand

Summary

In the main-window Shared Files directory tree (CSharedDirsTreeCtrl, the "All Directories" file-system browse tree), expanding a folder and then collapsing it leaves the expand glyph stuck at -. The control treats the node as still expanded, so the next click is consumed as a phantom collapse and the folder can no longer be expanded.

This is a regression from stock/community eMule behavior, not a stock defect.

Root Cause

CSharedDirsTreeCtrl::OnTvnItemexpanding (in srchybrid/SharedDirsTreeCtrl.cpp) handles TVN_ITEMEXPANDING, which fires before the expand/collapse completes. Commit ec9e1d52 (BUG-022) added a bCollapse branch that, on collapse, calls DeleteChildItems(pExpanded) to free the subtree but does not re-add it:

  • The node is still flagged TVIS_EXPANDED and still owns its children when the notification fires.
  • Deleting every child mid-collapse makes the tree-view auto-clear the expanded state, then the control runs its own collapse toggle on an already-cleared node. The result is a desynced TVIS_EXPANDED: glyph shows -, node is logically collapsed, and the next click is eaten as a collapse.

Stock/community eMule never branches on collapse - it unconditionally re-adds the immediate subdirectories, so the node always retains children and the control's collapse toggle runs cleanly.

Why The Fix Choice Matters At Scale

The tree holds directory nodes only (files live in CSharedFilesCtrl), populated lazily one level per expand via FileSystemTreeAddSubdirectories (a live directory enumeration). Three correct fixes were considered against 50k+ shared libraries:

  • Restore stock (always re-add on collapse): correct, but re-enumerates the immediate subdirs on every collapse - redundant disk I/O that makes collapsing a large folder as slow as expanding it.
  • No-op collapse: instant, but retains the entire previously-expanded subtree in memory until the next expand - worst memory retention on deep trees.
  • Free children in TVN_ITEMEXPANDED (chosen): collapse does no enumeration, frees the whole subtree (the memory win BUG-022 intended), and is correct because deletion runs after the collapse has settled.

Fix

  • OnTvnItemexpanding now handles only the expand direction (returns early on TVE_COLLAPSE/TVE_COLLAPSERESET); it populates children before the node draws expanded.
  • New OnTvnItemexpanded (TVN_ITEMEXPANDED, post-action) frees the now-hidden subtree on collapse via DeleteChildItems and keeps cChildren = 1 so the + glyph remains and the folder can be expanded again.
  • Both the SDI_UNSHAREDDIRECTORY folders and the SDI_FILESYSTEMPARENT root node use the same corrected ordering.
  • WHY: comments at both sites record the expanded-state desync and the post-collapse free.

Representative Sites

  • srchybrid/SharedDirsTreeCtrl.cpp: OnTvnItemexpanding, new OnTvnItemexpanded, message map TVN_ITEMEXPANDED reflect entry.
  • srchybrid/SharedDirsTreeCtrl.h: OnTvnItemexpanded declaration.

Acceptance Criteria

  • [ ] Expand, then collapse, then expand again toggles the +/- glyph correctly and re-expands every time, for folders and the root node.
  • [ ] Collapsing a large folder performs no directory re-enumeration.
  • [ ] Collapsed nodes free their materialized subtree (bounded memory on huge shared trees) and repopulate lazily on the next expand.
  • [x] Debug and Release x64 app builds pass before implementation commit.
  • [x] Diagnostics Release x64 build passes before implementation commit.

Resolution

Implemented and shipped in 0.7.3-rc.2. Fix commit 8acda717 ("BUG-148 fix shared dirs tree collapse re-expand") is an ancestor of the rc.2 app head 38827709 (tag emulebb-v0.7.3-rc.2). The expand-only OnTvnItemexpanding / post-collapse OnTvnItemexpanded split in srchybrid/SharedDirsTreeCtrl.cpp restores correct +/- glyph toggling and re-expansion while freeing the hidden subtree after the collapse settles. Build gates (x64 Debug/Release + diagnostics Release) passed before the commit. Closed DONE on 2026-06-13.