← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 2727: filter search results

 

------------------------------------------------------------
revno: 2727
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Sun 2011-12-18 14:41:12 +0100
message:
  filter search results
modified:
  changelog.txt
  dwt/include/dwt/widgets/TextBox.h
  dwt/src/widgets/TextBox.cpp
  help/window_search.html
  win32/SearchFrame.cpp
  win32/SearchFrame.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	2011-12-17 14:08:57 +0000
+++ changelog.txt	2011-12-18 13:41:12 +0000
@@ -52,7 +52,7 @@
 * Revamp style settings (poy)
 * Add user matching settings (poy)
 * [L#887021] No beep on ctrl+A in some text-boxes (poy)
-* Improve list filters (poy)
+* Improve list filters, add one to filter search results (poy)
 
 -- 0.782 2011-03-05 --
 * Prevent a remote crash triggered via malformed user commands (poy)

=== modified file 'dwt/include/dwt/widgets/TextBox.h'
--- dwt/include/dwt/widgets/TextBox.h	2011-12-17 14:08:57 +0000
+++ dwt/include/dwt/widgets/TextBox.h	2011-12-18 13:41:12 +0000
@@ -235,7 +235,7 @@
 	void setText(const tstring& txt);
 
 	/** Set a "cue banner text", a text that will be displayed in dim color when the control
-	doesn't have any text set. Only works for single-line controls. */
+	doesn't have any text set. Only works for single-line controls. Only available on >= Vista. */
 	void setCue(const tstring& text);
 
 	/// Returns the current selected text from the text box

=== modified file 'dwt/src/widgets/TextBox.cpp'
--- dwt/src/widgets/TextBox.cpp	2011-12-17 14:08:57 +0000
+++ dwt/src/widgets/TextBox.cpp	2011-12-18 13:41:12 +0000
@@ -93,8 +93,6 @@
 void TextBox::setCue(const tstring& text) {
 	if(util::win32::ensureVersion(util::win32::VISTA)) {
 		Edit_SetCueBannerTextFocused(handle(), text.c_str(), TRUE);
-	} else {
-		Edit_SetCueBannerText(handle(), text.c_str());
 	}
 }
 

=== modified file 'help/window_search.html'
--- help/window_search.html	2011-04-07 13:40:55 +0000
+++ help/window_search.html	2011-12-18 13:41:12 +0000
@@ -109,6 +109,11 @@
 <!--#include virtual="commands_user_favorites.inc" --> 
 <!--#include virtual="commands_listview.inc" --> 
 
+<h2>Filter search results</h2>
+<p cshelp="IDH_SEARCH_FILTER">
+These controls allow filtering the search results.
+</p>
+
 <h2>Status bar</h2>
 <dl style="margin-left: 40px;">
   <dt>Unnamed checkbox</dt>

=== modified file 'win32/SearchFrame.cpp'
--- win32/SearchFrame.cpp	2011-12-17 14:08:57 +0000
+++ win32/SearchFrame.cpp	2011-12-18 13:41:12 +0000
@@ -30,6 +30,7 @@
 #include <dwt/widgets/Grid.h>
 #include <dwt/widgets/SplitterContainer.h>
 
+#include "HoldRedraw.h"
 #include "resource.h"
 #include "TypedTable.h"
 
@@ -117,14 +118,12 @@
 size(0),
 sizeMode(0),
 fileType(0),
-slots(0),
 onlyFree(BOOLSETTING(SEARCH_ONLY_FREE_SLOTS)),
-filter(0),
-filterShared(BOOLSETTING(SEARCH_FILTER_SHARED)),
-merge(0),
-bMerge(BOOLSETTING(SEARCH_MERGE)),
+hideShared(BOOLSETTING(SEARCH_FILTER_SHARED)),
+merge(BOOLSETTING(SEARCH_MERGE)),
 hubs(0),
 results(0),
+filter(resultsColumns, COLUMN_LAST, [this] { updateList(); }),
 initialType(initialType_),
 droppedResults(0)
 {
@@ -214,22 +213,22 @@
 		CheckBox::Seed cs = WinUtil::Seeds::checkBox;
 		
 		cs.caption = T_("Only users with free slots");
-		slots = cur->addChild(cs);
-		slots->setHelpId(IDH_SEARCH_SLOTS);
-		slots->setChecked(onlyFree);
-		slots->onClicked([this] { handleSlotsClicked(); });
+		auto box = cur->addChild(cs);
+		box->setHelpId(IDH_SEARCH_SLOTS);
+		box->setChecked(onlyFree);
+		box->onClicked([this, box] { onlyFree = box->getChecked(); });
 
 		cs.caption = T_("Hide files already in share");
-		filter = cur->addChild(cs);
-		filter->setHelpId(IDH_SEARCH_SHARE);
-		filter->setChecked(filterShared);
-		filter->onClicked([this] { handleFilterClicked(); });
+		box = cur->addChild(cs);
+		box->setHelpId(IDH_SEARCH_SHARE);
+		box->setChecked(hideShared);
+		box->onClicked([this, box] { hideShared = box->getChecked(); });
 
 		cs.caption = T_("Merge results for the same file");
-		merge = cur->addChild(cs);
-		merge->setHelpId(IDH_SEARCH_MERGE);
-		merge->setChecked(bMerge);
-		merge->onClicked([this] { handleMergeClicked(); });
+		box = cur->addChild(cs);
+		box->setHelpId(IDH_SEARCH_MERGE);
+		box->setChecked(merge);
+		box->onClicked([this, box] { merge = box->getChecked(); });
 
 		gs.caption = T_("Hubs");
 		group = options->addChild(gs);
@@ -249,22 +248,47 @@
 		hubs->setChecked(0, false);
 	}
 
-	results = paned->addChild(WidgetResults::Seed(WinUtil::Seeds::table));
-	addWidget(results);
-
-	results->setSmallImageList(WinUtil::fileImages);
-	WinUtil::makeColumns(results, resultsColumns, COLUMN_LAST, SETTING(SEARCHFRAME_ORDER), SETTING(SEARCHFRAME_WIDTHS));
-
-	results->onDblClicked([this] { handleDownload(); });
-	results->onKeyDown([this](int c) { return handleKeyDown(c); });
-	results->onContextMenu([this](const dwt::ScreenCoordinate &sc) { return handleContextMenu(sc); });
+	{
+		auto cur = paned->addChild(Grid::Seed(2, 1));
+		cur->column(0).mode = GridInfo::FILL;
+		cur->row(0).mode = GridInfo::FILL;
+		cur->row(0).align = GridInfo::STRETCH;
+
+		results = cur->addChild(WidgetResults::Seed(WinUtil::Seeds::table));
+		addWidget(results);
+
+		results->setSmallImageList(WinUtil::fileImages);
+		WinUtil::makeColumns(results, resultsColumns, COLUMN_LAST, SETTING(SEARCHFRAME_ORDER), SETTING(SEARCHFRAME_WIDTHS));
+
+		results->onDblClicked([this] { handleDownload(); });
+		results->onKeyDown([this](int c) { return handleKeyDown(c); });
+		results->onContextMenu([this](const dwt::ScreenCoordinate &sc) { return handleContextMenu(sc); });
+
+		cur = cur->addChild(Grid::Seed(1, 4));
+		cur->column(1).mode = GridInfo::FILL;
+		cur->setHelpId(IDH_SEARCH_FILTER);
+
+		auto ls = WinUtil::Seeds::label;
+		ls.caption = T_("Filter search results:");
+		cur->addChild(ls);
+
+		filter.createTextBox(cur);
+		filter.text->setCue(T_("Filter search results"));
+		addWidget(filter.text);
+
+		filter.createColumnBox(cur);
+		addWidget(filter.column);
+
+		filter.createMethodBox(cur);
+		addWidget(filter.method);
+	}
 
 	initStatus();
 
 	{
 		auto showUI = addChild(WinUtil::Seeds::splitCheckBox);
 		showUI->setChecked(true);
-		showUI->onClicked([this, showUI] { paned->maximize(showUI->getChecked() ? nullptr : results); });
+		showUI->onClicked([this, showUI] { paned->maximize(showUI->getChecked() ? nullptr : results->getParent()); });
 		status->setWidget(STATUS_SHOW_UI, showUI);
 	}
 
@@ -495,67 +519,115 @@
 	}
 }
 
-void SearchFrame::addResult(SearchInfo* si) {
-	bool added = false;
-
-	// Newly added ones always have just one result - we combine here
-	dcassert(si->srs.size() == 1);
-	const SearchResultPtr& sr = si->srs[0];
+void SearchFrame::addResult(SearchResultPtr psr) {
+	auto& sr = *psr;
+
+	// Check that this is really a relevant search result...
+	if(currentSearch.empty()) {
+		return;
+	}
+	if(!sr.getToken().empty() && sr.getToken() != token) {
+		addDropped();
+		return;
+	}
+	if(isHash) {
+		if(sr.getType() != SearchResult::TYPE_FILE || TTHValue(Text::fromT(currentSearch[0])) != sr.getTTH()) {
+			addDropped();
+			return;
+		}
+	} else {
+		for(auto i = currentSearch.cbegin(), iend = currentSearch.cend(); i != iend; ++i) {
+			if((*i->begin() != _T('-') && Util::findSubString(sr.getFile(), Text::fromT(*i)) == tstring::npos) ||
+				(*i->begin() == _T('-') && i->size() != 1 && Util::findSubString(sr.getFile(), Text::fromT(i->substr(1))) != tstring::npos))
+			{
+				addDropped();
+				return;
+			}
+		}
+	}
+
+	// Drop results for already shared files
+	if(hideShared && ShareManager::getInstance()->isTTHShared(sr.getTTH())) {
+		addDropped();
+		return;
+	}
+
+	// Reject results without free slots
+	if(onlyFree && sr.getFreeSlots() < 1) {
+		addDropped();
+		return;
+	}
+
+	SearchInfo* si = nullptr;
 
 	// Check previous search results for dupes
-	for(int i = 0, iend = results->size(); !added && i < iend; ++i) {
-		SearchInfo* si2 = results->getData(i);
-		for(SearchResultList::iterator j = si2->srs.begin(), jend = si2->srs.end(); j != jend; ++j) {
-			SearchResultPtr& sr2 = *j;
-
-			bool sameUser = sr->getUser()->getCID() == sr2->getUser()->getCID();
-			if(sameUser && (sr->getFile() == sr2->getFile())) {
-				// dupe
-				delete si;
-				return;
-			} else if(sr->getType() == SearchResult::TYPE_FILE && sr2->getType() == SearchResult::TYPE_FILE && sr->getTTH() == sr2->getTTH()) {
-				if(sameUser || (sr->getSize() != sr2->getSize())) {
-					// dupe
-					delete si;
-					return;
+	for(auto i = searchResults.begin(), iend = searchResults.end(); !si && i != iend; ++i) {
+		auto& si2 = *i;
+
+		for(auto j = si2.srs.begin(), jend = si2.srs.end(); j != jend; ++j) {
+			auto& sr2 = **j;
+
+			bool sameUser = sr.getUser()->getCID() == sr2.getUser()->getCID();
+			if(sameUser && sr.getFile() == sr2.getFile()) {
+				return; // dupe
+			}
+			if(sr.getType() == SearchResult::TYPE_FILE && sr2.getType() == SearchResult::TYPE_FILE && sr.getTTH() == sr2.getTTH()) {
+				if(sameUser || sr.getSize() != sr2.getSize()) {
+					return; // dupe
 				}
 
-				if(bMerge) {
-					si2->srs.push_back(sr);
-					si2->update();
-					delete si;
-					added = true;
-					results->update(i);
+				if(merge) {
+					si2.srs.push_back(psr);
+					si2.update();
+					si = &si2;
 				}
 				break;
 			}
 		}
 	}
 
-	if(!added) {
-		results->insert(si);
-	}
-	status->setText(STATUS_COUNT, str(TFN_("%1% item", "%1% items", results->size()) % results->size()));
+	if(!si) {
+		searchResults.push_back(SearchInfo(psr));
+		si = &searchResults.back();
+	}
+
+	auto i = results->find(si);
+	if(filter.empty() || filter.match(filter.prepare(), [this, si](int column) { return Text::fromT(si->getText(column)); })) {
+		if(i == -1) {
+			results->insert(si);
+		} else {
+			results->update(i);
+		}
+	} else if(i != -1) {
+		results->erase(i);
+	}
+
+	updateStatusCount();
 	setDirty(SettingsManager::BOLD_SEARCH);
 }
 
+void SearchFrame::updateList() {
+	auto i = searchResults.begin();
+
+	auto filterPrep = filter.prepare();
+	auto filterInfoF = [this, &i](int column) { return Text::fromT(i->getText(column)); };
+
+	HoldRedraw h(results);
+	results->clear();
+	for(; i != searchResults.end(); ++i) {
+		if(filter.empty() || filter.match(filterPrep, filterInfoF)) {
+			results->insert(&*i);
+		}
+	}
+
+	updateStatusCount();
+}
+
 void SearchFrame::handlePurgeClicked() {
 	searchBox->clear();
 	lastSearches.clear();
 }
 
-void SearchFrame::handleSlotsClicked() {
-	onlyFree = slots->getChecked();
-}
-
-void SearchFrame::handleFilterClicked() {
-	filterShared = filter->getChecked();
-}
-
-void SearchFrame::handleMergeClicked() {
-	bMerge = merge->getChecked();
-}
-
 LRESULT SearchFrame::handleHubItemChanged(WPARAM wParam, LPARAM lParam) {
 	LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam;
 	if(lv->iItem == 0 && (lv->uNewState ^ lv->uOldState) & LVIS_STATEIMAGEMASK) {
@@ -677,9 +749,15 @@
 void SearchFrame::handleRemove() {
 	int i = -1;
 	while((i = results->getNext(-1, LVNI_SELECTED)) != -1) {
+		auto data = results->getData(i);
 		results->erase(i);
+		if(data) {
+			searchResults.erase(std::remove_if(searchResults.begin(), searchResults.end(),
+				[data](const SearchInfo& si) { return &si == data; }), searchResults.end());
+		}
 	}
-	status->setText(STATUS_COUNT, str(TFN_("%1% item", "%1% items", results->size()) % results->size()));
+
+	updateStatusCount();
 }
 
 struct UserCollector {
@@ -776,64 +854,8 @@
 	}
 }
 
-void SearchFrame::on(SearchManagerListener::SR, const SearchResultPtr& aResult) noexcept {
-	auto update = [this] { updateStatusFiltered(); };
-
-	// Check that this is really a relevant search result...
-	{
-		Lock l(cs);
-
-		if(currentSearch.empty()) {
-			return;
-		}
-
-		if(!aResult->getToken().empty() && token != aResult->getToken()) {
-			droppedResults++;
-			callAsync(update);
-			return;
-		}
-
-		if(isHash) {
-			if(aResult->getType() != SearchResult::TYPE_FILE || TTHValue(Text::fromT(currentSearch[0])) != aResult->getTTH()) {
-				droppedResults++;
-				callAsync(update);
-				return;
-			}
-		} else {
-			// match all here
-			for(TStringIter j = currentSearch.begin(); j != currentSearch.end(); ++j) {
-				if((*j->begin() != _T('-') && Util::findSubString(aResult->getFile(), Text::fromT(*j)) == tstring::npos) ||
-					(*j->begin() == _T('-') && j->size() != 1 && Util::findSubString(aResult->getFile(), Text::fromT(j->substr(1))) != tstring::npos)
-					)
-				{
-					droppedResults++;
-					callAsync(update);
-					return;
-				}
-			}
-		}
-	}
-
-	// Filter already shared files
-	if( filterShared ) {
-		const TTHValue& t = aResult->getTTH();
-		if( ShareManager::getInstance()->isTTHShared(t) ) {
-			droppedResults++;
-			callAsync(update);
-			return;
-		}
-	}
-
-	// Reject results without free slots
-	if((onlyFree && aResult->getFreeSlots() < 1))
-	{
-		droppedResults++;
-		callAsync(update);
-		return;
-	}
-
-	auto si = new SearchInfo(aResult);
-	callAsync([=] { addResult(si); });
+void SearchFrame::on(SearchManagerListener::SR, const SearchResultPtr& sr) noexcept {
+	callAsync([this, sr] { addResult(sr); });
 }
 
 void SearchFrame::onHubAdded(HubInfo* info) {
@@ -876,51 +898,42 @@
 }
 
 void SearchFrame::runSearch() {
-	StringList clients;
-
-	// Change Default Settings If Changed
-	if (onlyFree != BOOLSETTING(SEARCH_ONLY_FREE_SLOTS))
+	// change settings if changed
+	if(onlyFree != BOOLSETTING(SEARCH_ONLY_FREE_SLOTS))
 		SettingsManager::getInstance()->set(SettingsManager::SEARCH_ONLY_FREE_SLOTS, onlyFree);
-	if (filterShared != BOOLSETTING(SEARCH_FILTER_SHARED))
-		SettingsManager::getInstance()->set(SettingsManager::SEARCH_FILTER_SHARED, filterShared);
-	if (bMerge != BOOLSETTING(SEARCH_MERGE))
-		SettingsManager::getInstance()->set(SettingsManager::SEARCH_MERGE, bMerge);
+	if(hideShared != BOOLSETTING(SEARCH_FILTER_SHARED))
+		SettingsManager::getInstance()->set(SettingsManager::SEARCH_FILTER_SHARED, hideShared);
+	if(merge != BOOLSETTING(SEARCH_MERGE))
+		SettingsManager::getInstance()->set(SettingsManager::SEARCH_MERGE, merge);
 	if(initialType == SearchManager::TYPE_ANY) {
 		string text = Text::fromT(fileType->getText());
 		if(text != SETTING(LAST_SEARCH_TYPE))
 			SettingsManager::getInstance()->set(SettingsManager::LAST_SEARCH_TYPE, text);
 	}
 
-	tstring s = searchBox->getText();
-
+	auto s = searchBox->getText();
 	if(s.empty())
 		return;
 
-	int n = hubs->size();
-	for(int i = 0; i < n; i++) {
+	StringList clients;
+	for(size_t i = 0, n = hubs->size(); i < n; ++i) {
 		if(hubs->isChecked(i)) {
 			clients.push_back(Text::fromT(hubs->getData(i)->url));
 		}
 	}
-
 	if(clients.empty())
 		return;
 
-	tstring tsize = size->getText();
-
-	double lsize = Util::toDouble(Text::fromT(tsize));
+	auto lsize = Util::toDouble(Text::fromT(size->getText()));
 	switch(sizeMode->getSelected()) {
-	case 1:
-		lsize*=1024.0; break;
-	case 2:
-		lsize*=1024.0*1024.0; break;
-	case 3:
-		lsize*=1024.0*1024.0*1024.0; break;
+	case 1: lsize *= 1024.0; break;
+	case 2: lsize *= 1024.0 * 1024.0; break;
+	case 3: lsize *= 1024.0 * 1024.0 * 1024.0; break;
 	}
-
-	int64_t llsize = (int64_t)lsize;
+	auto llsize = static_cast<int64_t>(lsize);
 
 	results->clear();
+	searchResults.clear();
 
 	// Add new searches to the last-search dropdown list
 	if(find(lastSearches.begin(), lastSearches.end(), s) == lastSearches.end())
@@ -937,25 +950,22 @@
 		lastSearches.push_back(s);
 	}
 
-	{
-		Lock l(cs);
-		currentSearch = StringTokenizer<tstring>(s, ' ').getTokens();
-		s.clear();
-		//strip out terms beginning with -
-		for(TStringList::iterator si = currentSearch.begin(); si != currentSearch.end(); ) {
-			if(si->empty()) {
-				si = currentSearch.erase(si);
-				continue;
-			}
-			if ((*si)[0] != _T('-'))
-				s += *si + _T(' ');
-			++si;
+	currentSearch = StringTokenizer<tstring>(s, ' ').getTokens();
+	s.clear();
+	//strip out terms beginning with -
+	for(auto si = currentSearch.begin(); si != currentSearch.end();) {
+		if(si->empty()) {
+			si = currentSearch.erase(si);
+			continue;
 		}
-
-		s = s.substr(0, max(s.size(), static_cast<tstring::size_type>(1)) - 1);
-		token = Util::toString(Util::rand());
+		if((*si)[0] != _T('-'))
+			s += *si + _T(' ');
+		++si;
 	}
 
+	s = s.substr(0, max(s.size(), static_cast<tstring::size_type>(1)) - 1);
+	token = Util::toString(Util::rand());
+
 	SearchManager::SizeModes searchMode((SearchManager::SizeModes)mode->getSelected());
 	if(llsize == 0)
 		searchMode = SearchManager::SIZE_DONTCARE;
@@ -978,7 +988,7 @@
 
 	status->setText(STATUS_STATUS, str(TF_("Searching for %1%...") % s));
 	status->setText(STATUS_COUNT, Util::emptyStringT);
-	status->setText(STATUS_FILTERED, Util::emptyStringT);
+	status->setText(STATUS_DROPPED, Util::emptyStringT);
 	droppedResults = 0;
 	isHash = (ftype == SearchManager::TYPE_TTH);
 
@@ -989,22 +999,30 @@
 			(SearchManager::TypeModes)ftype, searchMode, token, extList);
 		if(BOOLSETTING(CLEAR_SEARCH)) // Only clear if the search was sent
 			searchBox->setText(Util::emptyStringT);
+
 	} else {
-		int32_t waitFor = SearchManager::getInstance()->timeToSearch();
+		auto waitFor = SearchManager::getInstance()->timeToSearch();
 		tstring msg = str(TFN_("Searching too soon, next search in %1% second", "Searching too soon, next search in %1% seconds", waitFor) % waitFor);
-
 		status->setText(STATUS_STATUS, msg);
-		status->setText(STATUS_COUNT, Util::emptyStringT);
-		status->setText(STATUS_FILTERED, Util::emptyStringT);
-
 		setText(str(TF_("Search - %1%") % msg));
 		// Start the countdown timer
 		initSecond();
 	}
 }
 
-void SearchFrame::updateStatusFiltered() {
-	status->setText(STATUS_FILTERED, str(TF_("%1% filtered") % droppedResults));
+void SearchFrame::updateStatusCount() {
+	auto current = results->size();
+	auto total = searchResults.size();
+	if(current >= total) {
+		status->setText(STATUS_COUNT, str(TFN_("%1% item", "%1% items", current) % current));
+	} else {
+		status->setText(STATUS_COUNT, str(TFN_("%1% / %2% item", "%1% / %2% items", current) % current % total));
+	}
+}
+
+void SearchFrame::addDropped() {
+	++droppedResults;
+	status->setText(STATUS_DROPPED, str(TF_("%1% dropped") % droppedResults));
 }
 
 void SearchFrame::initSecond() {

=== modified file 'win32/SearchFrame.h'
--- win32/SearchFrame.h	2011-11-21 19:04:08 +0000
+++ win32/SearchFrame.h	2011-12-18 13:41:12 +0000
@@ -19,16 +19,19 @@
 #ifndef DCPLUSPLUS_WIN32_SEARCH_FRAME_H
 #define DCPLUSPLUS_WIN32_SEARCH_FRAME_H
 
+#include <list>
 #include <set>
 
 #include <dcpp/Client.h>
+#include <dcpp/ClientManagerListener.h>
 #include <dcpp/SearchManager.h>
 #include <dcpp/SettingsManager.h>
-#include <dcpp/ClientManagerListener.h>
 
+#include "AspectUserCommand.h"
+#include "ListFilter.h"
 #include "MDIChildFrame.h"
-#include "AspectUserCommand.h"
 
+using std::list;
 using std::set;
 
 class SearchFrame :
@@ -44,7 +47,7 @@
 		STATUS_SHOW_UI,
 		STATUS_STATUS,
 		STATUS_COUNT,
-		STATUS_FILTERED,
+		STATUS_DROPPED,
 		STATUS_LAST
 	};
 
@@ -76,8 +79,7 @@
 		COLUMN_LAST
 	};
 
-	class SearchInfo {
-	public:
+	struct SearchInfo {
 		SearchInfo(const SearchResultPtr& aSR);
 		~SearchInfo();
 
@@ -171,23 +173,23 @@
 
 	ComboBoxPtr fileType;
 
-	CheckBoxPtr slots;
 	bool onlyFree;
-
-	CheckBoxPtr filter;
-	bool filterShared;
-
-	CheckBoxPtr merge;
-	bool bMerge;
+	bool hideShared;
+	bool merge;
 
 	typedef TypedTable<HubInfo> WidgetHubs;
 	typedef WidgetHubs* WidgetHubsPtr;
 	WidgetHubsPtr hubs;
 
-	typedef TypedTable<SearchInfo> WidgetResults;
+	typedef TypedTable<SearchInfo, false> WidgetResults;
 	typedef WidgetResults* WidgetResultsPtr;
 	WidgetResultsPtr results;
 
+	list<SearchInfo> searchResults; /* the LPARAM data of table entries are direct pointers to
+									objects stored by this container, hence the std::list. */
+
+	ListFilter filter;
+
 	SearchManager::TypeModes initialType;
 
 	static TStringList lastSearches;
@@ -197,8 +199,6 @@
 	TStringList currentSearch;
 	StringList targets;
 
-	CriticalSection cs;
-
 	ParamMap ucLineParams;
 
 	std::string token;
@@ -208,7 +208,7 @@
 
 	void handlePurgeClicked();
 	void handleSlotsClicked();
-	void handleFilterClicked();
+	void handleHideSharedClicked();
 	void handleMergeClicked();
 	LRESULT handleHubItemChanged(WPARAM wParam, LPARAM lParam);
 	bool handleKeyDown(int c);
@@ -239,17 +239,17 @@
 
 	void runUserCommand(const UserCommand& uc);
 	void runSearch();
-	void updateStatusFiltered();
+	void updateStatusCount();
+	void addDropped();
+	void addResult(SearchResultPtr psr);
+	void updateList();
 
 	MenuPtr makeMenu();
 	void addTargetMenu(const MenuPtr& parent, const StringPairList& favoriteDirs, const SearchInfo::CheckTTH& checkTTH);
 	void addTargetDirMenu(const MenuPtr& parent, const StringPairList& favoriteDirs);
 
-	WidgetResultsPtr getUserList() { return results; }
-
 	// SearchManagerListener
-	virtual void on(SearchManagerListener::SR, const SearchResultPtr& aResult) noexcept;
-	void addResult(SearchInfo* si);
+	virtual void on(SearchManagerListener::SR, const SearchResultPtr& sr) noexcept;
 
 	// SettingsManagerListener
 	virtual void on(SettingsManagerListener::SearchTypesChanged) noexcept;