← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 2994: add a virtual tree control to deal with large trees (only inserts nodes when expanding their parent)

 

------------------------------------------------------------
revno: 2994
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Thu 2012-07-12 22:49:11 +0200
message:
  add a virtual tree control to deal with large trees (only inserts nodes when expanding their parent)
added:
  dwt/include/dwt/widgets/VirtualTree.h
  dwt/src/widgets/VirtualTree.cpp
modified:
  changelog.txt
  win32/DirectoryListingFrame.cpp
  win32/DirectoryListingFrame.h


--
lp:dcplusplus
https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk

Your team Dcplusplus-team is subscribed to branch lp:dcplusplus.
To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'changelog.txt'
--- changelog.txt	2012-07-12 13:17:54 +0000
+++ changelog.txt	2012-07-12 20:49:11 +0000
@@ -26,6 +26,9 @@
 * [dwt] Add a link control (poy)
 * Allow Magnet links to be pasted in the "Quick connect" box (poy)
 * Rise the minislot size to 512 KiB
+* More Alt+I shortcuts for list filters (emtee)
+* Increase the chat buffer limit (iceman50)
+* Eliminate GUI freezes when opening large file lists (poy)
 
 -- 0.799 2012-05-05 --
 * Add icons (iceman50)

=== added file 'dwt/include/dwt/widgets/VirtualTree.h'
--- dwt/include/dwt/widgets/VirtualTree.h	1970-01-01 00:00:00 +0000
+++ dwt/include/dwt/widgets/VirtualTree.h	2012-07-12 20:49:11 +0000
@@ -0,0 +1,134 @@
+/*
+  DC++ Widget Toolkit
+
+  Copyright (c) 2007-2012, Jacek Sieka
+
+  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 the DWT 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.
+*/
+
+#ifndef DWT_VIRTUALTREE_H
+#define DWT_VIRTUALTREE_H
+
+#include <list>
+
+#include <boost/optional.hpp>
+
+#include "Tree.h"
+
+namespace dwt {
+
+/** This is a tree control designed to hold a large amount of items. Instead of adding all of its
+nodes to the control, it stores them internally and only adds them when required (when expanding
+parent nodes). */
+/* Implementation: Fake HTREEITEMs are delivered to the callers of a VirtualTree. Instead of
+pointing to actual nodes of the tree control, they point to an internal structure. Tree control
+messages that depend on HTREEITEMs are captured and handled internally by the VirtualTree.
+Many implementation details are inspired by Wine (dlls/comctl32/treeview.c). */
+class VirtualTree :
+	public Tree
+{
+	typedef Tree BaseType;
+	friend class WidgetCreator<VirtualTree>;
+
+public:
+	typedef VirtualTree ThisType;
+	typedef ThisType* ObjectType;
+
+	struct Seed : public BaseType::Seed {
+		typedef ThisType WidgetType;
+
+		Seed(const BaseType::Seed& seed);
+	};
+
+	virtual bool handleMessage(const MSG& msg, LRESULT& retVal);
+
+protected:
+	explicit VirtualTree(Widget* parent);
+	virtual ~VirtualTree() { }
+
+private:
+	/* info about each item; similar to TVINSERTSTRUCT / TVITEMEX. a pointer to this structure is
+	given to callers of a VirtualTree as an HTREEITEM; they can then manipulate it as usual. */
+	struct Item : boost::noncopyable {
+		HTREEITEM handle; /* the handle in the actual tree control when this item is visible, which
+						  is different from the fake handle given to callers of a VirtualTree. */
+		Item* parent;
+		Item* prev;
+		Item* next;
+		Item* firstChild;
+		Item* lastChild;
+		UINT mask;
+		UINT state;
+		boost::optional<tstring> text;
+		int image;
+		int selectedImage;
+		LPARAM lParam;
+
+		Item();
+		Item(TVINSERTSTRUCT& tvis);
+
+		HTREEITEM ptr() const;
+
+		void insertBefore(Item* sibling);
+		void insertAfter(Item* sibling);
+		void setText(LPTSTR text);
+
+		bool expanded() const;
+		Item* lastExpandedChild() const;
+		Item* prevVisible() const;
+		Item* nextVisible() const;
+	};
+
+	std::list<Item> items;
+	Item* root;
+	Item* selected;
+
+	bool handleDelete(LPARAM lParam);
+	bool handleEnsureVisible(Item* item);
+	bool handleExpand(WPARAM code, Item* item);
+	void handleExpanded(NMTREEVIEW& data);
+	void handleExpanding(NMTREEVIEW& data);
+	bool handleGetItem(TVITEMEX& tv);
+	UINT handleGetItemState(UINT mask, Item* item);
+	Item* handleGetNextItem(WPARAM code, Item* item);
+	Item* handleInsert(TVINSERTSTRUCT& tvis);
+	bool handleSelect(WPARAM code, Item* item);
+	void handleSelected(Item* item);
+	bool handleSetItem(TVITEMEX& tv);
+
+	void addRoot();
+	bool validate(Item* item) const;
+	Item* find(HTREEITEM handle);
+	void display(Item& item);
+	void hide(Item& item);
+	void remove(Item* item);
+	void updateChildDisplay(Item* item);
+	LRESULT sendTreeMsg(UINT msg, WPARAM wParam, LPARAM lParam);
+};
+
+}
+
+#endif

=== added file 'dwt/src/widgets/VirtualTree.cpp'
--- dwt/src/widgets/VirtualTree.cpp	1970-01-01 00:00:00 +0000
+++ dwt/src/widgets/VirtualTree.cpp	2012-07-12 20:49:11 +0000
@@ -0,0 +1,485 @@
+/*
+  DC++ Widget Toolkit
+
+  Copyright (c) 2007-2012, Jacek Sieka
+
+  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 the DWT 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 <dwt/widgets/VirtualTree.h>
+
+#include <dwt/util/check.h>
+
+namespace dwt {
+
+VirtualTree::Seed::Seed(const BaseType::Seed& seed) :
+	BaseType::Seed(seed)
+{
+}
+
+VirtualTree::VirtualTree(Widget* parent) :
+	BaseType(parent),
+	selected(nullptr)
+{
+	addRoot();
+}
+
+bool VirtualTree::handleMessage(const MSG& msg, LRESULT& retVal) {
+	switch(msg.message) {
+	case TVM_DELETEITEM:
+		{
+			retVal = handleDelete(msg.lParam);
+			return true;
+		}
+	case TVM_ENSUREVISIBLE:
+		{
+			retVal = handleEnsureVisible(reinterpret_cast<Item*>(msg.lParam));
+			return true;
+		}
+	case TVM_EXPAND:
+		{
+			retVal = handleExpand(msg.wParam, reinterpret_cast<Item*>(msg.lParam));
+			return true;
+		}
+	case TVM_GETCOUNT:
+		{
+			retVal = items.size();
+			return true;
+		}
+	case TVM_GETITEM:
+		{
+			retVal = handleGetItem(*reinterpret_cast<TVITEMEX*>(msg.lParam));
+			return true;
+		}
+	case TVM_GETITEMSTATE:
+		{
+			retVal = handleGetItemState(msg.lParam, reinterpret_cast<Item*>(msg.wParam));
+			return true;
+		}
+	case TVM_GETNEXTITEM:
+		{
+			auto ret = handleGetNextItem(msg.wParam, reinterpret_cast<Item*>(msg.lParam));
+			retVal = ret == root ? 0 : reinterpret_cast<LRESULT>(ret);
+			return true;
+		}
+	case TVM_HITTEST:
+		{
+			retVal = reinterpret_cast<LRESULT>(find(reinterpret_cast<HTREEITEM>(sendTreeMsg(TVM_HITTEST, 0, msg.lParam))));
+			reinterpret_cast<TVHITTESTINFO*>(msg.lParam)->hItem = reinterpret_cast<HTREEITEM>(retVal);
+			return true;
+		}
+	case TVM_INSERTITEM:
+		{
+			retVal = reinterpret_cast<LRESULT>(handleInsert(*reinterpret_cast<TVINSERTSTRUCT*>(msg.lParam)));
+			return true;
+		}
+	case TVM_SELECTITEM:
+		{
+			retVal = handleSelect(msg.wParam, reinterpret_cast<Item*>(msg.lParam));
+			return true;
+		}
+	case TVM_SETITEM:
+		{
+			retVal = handleSetItem(*reinterpret_cast<TVITEMEX*>(msg.lParam));
+			return true;
+		}
+	case WM_NOTIFY:
+		{
+			switch(reinterpret_cast<NMHDR*>(msg.lParam)->code) {
+			case TVN_ITEMEXPANDED:
+				{
+					handleExpanded(*reinterpret_cast<NMTREEVIEW*>(msg.lParam));
+					break;
+				}
+			case TVN_ITEMEXPANDING:
+				{
+					handleExpanding(*reinterpret_cast<NMTREEVIEW*>(msg.lParam));
+					break;
+				}
+			case TVN_SELCHANGED:
+				{
+					handleSelected(find(reinterpret_cast<NMTREEVIEW*>(msg.lParam)->itemNew.hItem));
+					break;
+				}
+			}
+			break;
+		}
+	}
+	return BaseType::handleMessage(msg, retVal);
+}
+
+// root node
+VirtualTree::Item::Item() :
+	handle(nullptr),
+	parent(nullptr),
+	prev(nullptr),
+	next(nullptr),
+	firstChild(nullptr),
+	lastChild(nullptr),
+	mask(0),
+	state(TVIS_EXPANDED),
+	image(0),
+	selectedImage(0),
+	lParam(0)
+{
+}
+
+VirtualTree::Item::Item(TVINSERTSTRUCT& tvis) :
+	handle(nullptr),
+	parent(reinterpret_cast<Item*>(tvis.hParent)),
+	prev(nullptr),
+	next(nullptr),
+	firstChild(nullptr),
+	lastChild(nullptr),
+	mask(tvis.itemex.mask),
+	state(tvis.itemex.state & tvis.itemex.stateMask),
+	image(tvis.itemex.iImage),
+	selectedImage(tvis.itemex.iSelectedImage),
+	lParam(tvis.itemex.lParam)
+{
+	if(tvis.hInsertAfter == TVI_FIRST) {
+		insertBefore(parent->firstChild);
+	} else if(tvis.hInsertAfter == TVI_LAST) {
+		insertAfter(parent->lastChild);
+	} else if(tvis.hInsertAfter == TVI_SORT) {
+		dwtDebugFail("VirtualTree TVI_SORT not implemented");
+	} else {
+		insertAfter(reinterpret_cast<Item*>(tvis.hInsertAfter));
+	}
+	setText(tvis.itemex.pszText);
+}
+
+HTREEITEM VirtualTree::Item::ptr() const {
+	return reinterpret_cast<HTREEITEM>(const_cast<Item*>(this));
+}
+
+void VirtualTree::Item::insertBefore(Item* sibling) {
+	if(sibling) {
+		if(sibling->prev) {
+			sibling->prev->next = this;
+		}
+		prev = sibling->prev;
+		sibling->prev = this;
+	}
+	next = sibling;
+	if(parent->firstChild == sibling) {
+		parent->firstChild = this;
+	}
+	if(!parent->lastChild) {
+		parent->lastChild = this;
+	}
+}
+
+void VirtualTree::Item::insertAfter(Item* sibling) {
+	if(sibling) {
+		if(sibling->next) {
+			sibling->next->prev = this;
+		}
+		next = sibling->next;
+		sibling->next = this;
+	}
+	prev = sibling;
+	if(parent->lastChild == sibling) {
+		parent->lastChild = this;
+	}
+	if(!parent->firstChild) {
+		parent->firstChild = this;
+	}
+}
+
+void VirtualTree::Item::setText(LPTSTR text) {
+	if(text && text != LPSTR_TEXTCALLBACK) {
+		this->text = text;
+	} else {
+		this->text.reset();
+	}
+}
+
+bool VirtualTree::Item::expanded() const {
+	return state & TVIS_EXPANDED;
+}
+
+VirtualTree::Item* VirtualTree::Item::lastExpandedChild() const {
+	return lastChild && expanded() ? lastChild->lastExpandedChild() : const_cast<Item*>(this);
+}
+
+VirtualTree::Item* VirtualTree::Item::prevVisible() const {
+	return prev ? prev->lastExpandedChild() : parent;
+}
+
+VirtualTree::Item* VirtualTree::Item::nextVisible() const {
+	if(firstChild && expanded()) { return firstChild; }
+	if(next) { return next; }
+	auto item = this;
+	while(item->parent) {
+		item = item->parent;
+		if(item->next) {
+			return item->next;
+		}
+	}
+	return nullptr;
+}
+
+bool VirtualTree::handleDelete(LPARAM lParam) {
+	if(!lParam || lParam == reinterpret_cast<LPARAM>(TVI_ROOT)) {
+		bool ret = sendTreeMsg(TVM_DELETEITEM, 0, lParam);
+		items.clear();
+		addRoot();
+		return ret;
+	}
+	auto item = reinterpret_cast<Item*>(lParam);
+	if(!validate(item)) { return false; }
+	hide(*item);
+	remove(item);
+	return true;
+}
+
+bool VirtualTree::handleEnsureVisible(Item* item) {
+	if(!validate(item)) { return false; }
+	display(*item);
+	return sendTreeMsg(TVM_ENSUREVISIBLE, 0, reinterpret_cast<LPARAM>(item->handle));
+}
+
+bool VirtualTree::handleExpand(WPARAM code, Item* item) {
+	if(!validate(item)) { return false; }
+	switch(code) {
+	case TVE_COLLAPSE: if(item->expanded()) { item->state &= ~TVIS_EXPANDED; } break;
+	case TVE_EXPAND: if(!item->expanded()) { item->state |= TVIS_EXPANDED; } break;
+	default: dwtDebugFail("VirtualTree expand code not implemented"); break;
+	}
+	if(item->handle) {
+		sendTreeMsg(TVM_EXPAND, code, reinterpret_cast<LPARAM>(item->handle));
+	}
+	return true;
+}
+
+void VirtualTree::handleExpanded(NMTREEVIEW& data) {
+	auto item = find(data.itemNew.hItem);
+	if(!validate(item)) { return; }
+	switch(data.action) {
+	case TVE_COLLAPSE:
+		{
+			if(item->expanded()) { item->state &= ~TVIS_EXPANDED; }
+			auto child = item->firstChild;
+			while(child) {
+				hide(*child);
+				child = child->next;
+			}
+			break;
+		}
+	case TVE_EXPAND:
+		{
+			if(!item->expanded()) { item->state |= TVIS_EXPANDED; }
+			break;
+		}
+	default: dwtDebugFail("VirtualTree expand code not implemented"); break;
+	}
+}
+
+void VirtualTree::handleExpanding(NMTREEVIEW& data) {
+	auto item = find(data.itemNew.hItem);
+	if(!validate(item)) { return; }
+	switch(data.action) {
+	case TVE_COLLAPSE:
+		{
+			break;
+		}
+	case TVE_EXPAND:
+		{
+			auto child = item->firstChild;
+			while(child) {
+				display(*child);
+				child = child->next;
+			}
+			break;
+		}
+	default: dwtDebugFail("VirtualTree expand code not implemented"); break;
+	}
+}
+
+bool VirtualTree::handleGetItem(TVITEMEX& tv) {
+	auto item = reinterpret_cast<Item*>(tv.hItem);
+	if(!validate(item)) { return false; }
+	if(tv.mask & TVIF_CHILDREN) { tv.cChildren = item->firstChild ? 1 : 0; }
+	if(tv.mask & TVIF_HANDLE) { tv.hItem = item->ptr(); }
+	if(tv.mask & TVIF_IMAGE) { tv.iImage = item->image; }
+	tv.lParam = item->lParam; // doesn't depend on TVIF_PARAM
+	if(tv.mask & TVIF_SELECTEDIMAGE) { tv.iSelectedImage = item->selectedImage; }
+	tv.state = item->state; // doesn't depend on TVIF_STATE nor on stateMask
+	if(tv.mask & TVIF_TEXT && item->text) { item->text->copy(tv.pszText, tv.cchTextMax); }
+	return true;
+}
+
+UINT VirtualTree::handleGetItemState(UINT mask, Item* item) {
+	if(!validate(item)) { return 0; }
+	return item->state & mask;
+}
+
+VirtualTree::Item* VirtualTree::handleGetNextItem(WPARAM code, Item* item) {
+	if(item && !validate(item)) { return nullptr; }
+	switch(code) {
+	case TVGN_CARET: return selected;
+	case TVGN_CHILD: return (item ? item : root)->firstChild;
+	case TVGN_DROPHILITE: return nullptr;
+	case TVGN_FIRSTVISIBLE: return root->firstChild;
+	case TVGN_LASTVISIBLE: return root->lastExpandedChild();
+	case TVGN_NEXT: return item ? item->next : nullptr;
+	//case TVGN_NEXTSELECTED: return selected;
+	case TVGN_NEXTVISIBLE: return item ? item->nextVisible() : nullptr;
+	case TVGN_PARENT: return item ? item->parent : nullptr;
+	case TVGN_PREVIOUS: return item ? item->prev : nullptr;
+	case TVGN_PREVIOUSVISIBLE: return item ? item->prevVisible() : nullptr;
+	case TVGN_ROOT: return root->firstChild;
+	default: return nullptr;
+	}
+}
+
+VirtualTree::Item* VirtualTree::handleInsert(TVINSERTSTRUCT& tvis) {
+	if(tvis.hParent == TVI_ROOT || !tvis.hParent) {
+		tvis.hParent = root->ptr();
+	}
+	items.emplace_back(tvis);
+	auto& item = items.back();
+	if(item.parent->firstChild == item.parent->lastChild) {
+		updateChildDisplay(item.parent);
+	}
+	if(item.parent->expanded()) {
+		display(item);
+	}
+	return &item;
+}
+
+bool VirtualTree::handleSelect(WPARAM code, Item* item) {
+	if(!validate(item)) { return sendTreeMsg(TVM_SELECTITEM, code, 0); }
+	display(*item);
+	return sendTreeMsg(TVM_SELECTITEM, code, reinterpret_cast<LPARAM>(item->handle));
+}
+
+void VirtualTree::handleSelected(Item* item) {
+	selected = validate(item) ? item : nullptr;
+}
+
+bool VirtualTree::handleSetItem(TVITEMEX& tv) {
+	auto item = reinterpret_cast<Item*>(tv.hItem);
+	if(!validate(item)) { return false; }
+	if(tv.mask & TVIF_IMAGE) { item->image = tv.iImage; }
+	if(tv.mask & TVIF_PARAM) { item->lParam = tv.lParam; }
+	if(tv.mask & TVIF_SELECTEDIMAGE) { item->selectedImage = tv.iSelectedImage; }
+	if(tv.mask & TVIF_STATE) { item->state &= ~tv.stateMask; item->state |= tv.state & tv.stateMask; }
+	if(tv.mask & TVIF_TEXT) { item->setText(tv.pszText); }
+	if(item->handle) {
+		tv.hItem = item->handle;
+		sendTreeMsg(TVM_SETITEM, 0, reinterpret_cast<LPARAM>(&tv));
+	}
+	return true;
+}
+
+void VirtualTree::addRoot() {
+	items.emplace_back();
+	root = &items.back();
+}
+
+bool VirtualTree::validate(Item* item) const {
+	return item;
+	/* ideally one would check that the item does point to an element of the "items" list, but
+	iterating through the list takes too long when it's a large one. */
+}
+
+VirtualTree::Item* VirtualTree::find(HTREEITEM handle) {
+	if(!handle) { return nullptr; }
+	for(auto& i: items) { if(i.handle == handle) { return &i; } }
+	return nullptr;
+}
+
+void VirtualTree::display(Item& item) {
+	if(item.handle) { return; }
+	if(item.parent != root && !item.parent->handle) { display(*item.parent); }
+	TVINSERTSTRUCT tvis = { item.parent->handle, item.prev ? item.prev->handle : item.next ? TVI_FIRST : TVI_LAST, { {
+		TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT } } };
+	auto expanded = item.expanded();
+	if(expanded) { item.state &= ~TVIS_EXPANDED; }
+	tvis.itemex.state = item.state;
+	tvis.itemex.stateMask = item.state;
+	tvis.itemex.pszText = item.text ? const_cast<TCHAR*>(item.text->c_str()) : LPSTR_TEXTCALLBACK;
+	tvis.itemex.iImage = item.image;
+	tvis.itemex.iSelectedImage = item.selectedImage;
+	tvis.itemex.cChildren = item.firstChild ? 1 : 0;
+	tvis.itemex.lParam = item.lParam;
+	item.handle = reinterpret_cast<HTREEITEM>(sendTreeMsg(TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>(&tvis)));
+	if(expanded) { sendTreeMsg(TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(item.handle)); }
+}
+
+void VirtualTree::hide(Item& item) {
+	if(!item.handle) { return; }
+	auto child = item.firstChild;
+	while(child) {
+		hide(*child);
+		child = child->next;
+	}
+	sendTreeMsg(TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(item.handle));
+	item.handle = nullptr;
+}
+
+void VirtualTree::remove(Item* item) {
+	while(item->firstChild) {
+		remove(item->firstChild);
+	}
+	if(item->parent) {
+		if(item->parent->firstChild == item) {
+			item->parent->firstChild = item->next;
+			if(!item->parent->firstChild) {
+				updateChildDisplay(item->parent);
+			}
+		}
+		if(item->parent->lastChild == item) {
+			item->parent->lastChild = item->prev;
+		}
+	}
+	if(item->prev) { item->prev->next = item->next; }
+	if(item->next) { item->next->prev = item->prev; }
+	if(item == selected) { selected = nullptr; }
+	for(auto i = items.begin(), iend = items.end(); i != iend; ++i) {
+		if(&*i == item) {
+			items.erase(i);
+			break;
+		}
+	}
+}
+
+void VirtualTree::updateChildDisplay(Item* item) {
+	if(!item->handle) { return; }
+	TVITEMEX tv = { TVIF_CHILDREN, item->handle };
+	tv.cChildren = item->firstChild ? 1 : 0;
+	sendTreeMsg(TVM_SETITEM, 0, reinterpret_cast<LPARAM>(&tv));
+}
+
+LRESULT VirtualTree::sendTreeMsg(UINT msg, WPARAM wParam, LPARAM lParam) {
+	// send with a direct dispatcher call to avoid loops (since we catch tree messages).
+	MSG treeMsg = { treeHandle(), msg, wParam, lParam };
+	return tree->getDispatcher().chain(treeMsg);
+}
+
+}

=== modified file 'win32/DirectoryListingFrame.cpp'
--- win32/DirectoryListingFrame.cpp	2012-07-12 17:50:02 +0000
+++ win32/DirectoryListingFrame.cpp	2012-07-12 20:49:11 +0000
@@ -37,7 +37,7 @@
 #include <dwt/widgets/SaveDialog.h>
 #include <dwt/widgets/SplitterContainer.h>
 #include <dwt/widgets/ToolBar.h>
-#include <dwt/widgets/Tree.h>//#include <dwt/widgets/VirtualTree.h>
+#include <dwt/widgets/VirtualTree.h>
 
 #include "TypedTable.h"
 #include "TypedTree.h"
@@ -439,6 +439,7 @@
 	ErrorF errorF;
 
 	void cacheInfo(DirectoryListing::Directory* d) {
+		if(parent.dl->getAbort()) { throw Exception(); }
 		for(auto i: d->directories) {
 			parent.dirCache[i] = make_unique<DirectoryListingFrame::ItemInfo>(i);
 			cacheInfo(i);
@@ -494,6 +495,7 @@
 		// error callback
 		error = std::move(s);
 		finishLoad();
+		dirCache.clear();
 	}); });
 
 	try {

=== modified file 'win32/DirectoryListingFrame.h'
--- win32/DirectoryListingFrame.h	2012-07-12 17:50:02 +0000
+++ win32/DirectoryListingFrame.h	2012-07-12 20:49:11 +0000
@@ -174,7 +174,7 @@
 	ComboBoxPtr searchBox;
 	ComboBoxPtr filterMethod;
 
-	typedef TypedTree<ItemInfo, true/*, dwt::VirtualTree*/> WidgetDirs;
+	typedef TypedTree<ItemInfo, true, dwt::VirtualTree> WidgetDirs;
 	typedef WidgetDirs* WidgetDirsPtr;
 	WidgetDirsPtr dirs;