diff options
Diffstat (limited to 'Swift/Controllers/Roster')
| -rw-r--r-- | Swift/Controllers/Roster/ContactRosterItem.cpp | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/GroupRosterItem.cpp | 25 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/GroupRosterItem.h | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/LeastCommonSubsequence.h | 106 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/Roster.cpp | 6 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp | 1 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/RosterItem.cpp | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/RosterItem.h | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/TableRoster.cpp | 184 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/TableRoster.h | 83 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp | 308 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp | 1 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/UnitTest/RosterTest.cpp | 17 | ||||
| -rw-r--r-- | Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp | 91 | 
14 files changed, 825 insertions, 13 deletions
| diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 0fe88aa..bbe81e6 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -7,6 +7,8 @@  #include "Swift/Controllers/Roster/ContactRosterItem.h"  #include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swiften/Base/foreach.h> +  namespace Swift { @@ -89,7 +91,7 @@ void ContactRosterItem::applyPresence(const std::string& resource, boost::shared  				presences_.erase(resource);  			}  		} -		if (presences_.size() == 0) { +		if (presences_.empty()) {  			offlinePresence_ = presence;  		}  	} else { diff --git a/Swift/Controllers/Roster/GroupRosterItem.cpp b/Swift/Controllers/Roster/GroupRosterItem.cpp index f0a377a..2a7bfa5 100644 --- a/Swift/Controllers/Roster/GroupRosterItem.cpp +++ b/Swift/Controllers/Roster/GroupRosterItem.cpp @@ -12,7 +12,7 @@  namespace Swift { -GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus) { +GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus), manualSort_(false) {  	expanded_ = true;  } @@ -20,6 +20,20 @@ GroupRosterItem::~GroupRosterItem() {  } +void GroupRosterItem::setManualSort(const std::string& manualSortValue) { +	manualSort_ = true; +	bool changed = manualSortValue_ != manualSortValue; +	manualSortValue_ = manualSortValue; +	if (changed) { +		onChildrenChanged(); +		onDataChanged(); +	} +} + +const std::string& GroupRosterItem::getSortableDisplayName() const { +	return manualSort_ ? manualSortValue_ : RosterItem::getSortableDisplayName(); +} +  bool GroupRosterItem::isExpanded() const {  	return expanded_;  } @@ -75,7 +89,7 @@ void GroupRosterItem::removeAll() {  				delete group;  			}  		} -		it++; +		++it;  	}  	children_.clear();  } @@ -103,7 +117,7 @@ ContactRosterItem* GroupRosterItem::removeChild(const JID& jid) {  				removed = groupRemoved;  			}  		} -		it++; +		++it;  	}  	onChildrenChanged();  	onDataChanged(); @@ -122,7 +136,7 @@ GroupRosterItem* GroupRosterItem::removeGroupChild(const std::string& groupName)  			it = children_.erase(it);  			continue;  		} -		it++; +		++it;  	}  	onChildrenChanged();  	onDataChanged(); @@ -210,7 +224,8 @@ void GroupRosterItem::handleChildrenChanged(GroupRosterItem* group) {  	} else {  		displayedChildren_.erase(std::remove(displayedChildren_.begin(), displayedChildren_.end(), group), displayedChildren_.end());  	} -	if (oldSize != getDisplayedChildren().size()) { + +	if (oldSize != getDisplayedChildren().size() || sortDisplayed()) {  		onChildrenChanged();  		onDataChanged();  	} diff --git a/Swift/Controllers/Roster/GroupRosterItem.h b/Swift/Controllers/Roster/GroupRosterItem.h index 57fa9fa..beb7705 100644 --- a/Swift/Controllers/Roster/GroupRosterItem.h +++ b/Swift/Controllers/Roster/GroupRosterItem.h @@ -31,6 +31,8 @@ class GroupRosterItem : public RosterItem {  		void setExpanded(bool expanded);  		bool isExpanded() const;  		boost::signal<void (bool)> onExpandedChanged; +		void setManualSort(const std::string& manualSortValue); +		virtual const std::string& getSortableDisplayName() const;  	private:  		void handleChildrenChanged(GroupRosterItem* group);  		void handleDataChanged(RosterItem* item); @@ -40,6 +42,8 @@ class GroupRosterItem : public RosterItem {  		std::vector<RosterItem*> children_;  		std::vector<RosterItem*> displayedChildren_;  		bool sortByStatus_; +		bool manualSort_; +		std::string manualSortValue_;  };  } diff --git a/Swift/Controllers/Roster/LeastCommonSubsequence.h b/Swift/Controllers/Roster/LeastCommonSubsequence.h new file mode 100644 index 0000000..dd3c95a --- /dev/null +++ b/Swift/Controllers/Roster/LeastCommonSubsequence.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +namespace Swift { +	using std::equal_to; + +	namespace Detail { +		template<typename XIt, typename YIt, typename Length, typename Predicate> +		void computeLeastCommonSubsequenceMatrix(XIt xBegin, XIt xEnd, YIt yBegin, YIt yEnd, std::vector<Length>& result) { +			size_t width = std::distance(xBegin, xEnd) + 1; +			size_t height = std::distance(yBegin, yEnd)  + 1; +			result.resize(width * height); + +			// Initialize first row & column +			for (size_t i = 0; i < width; ++i) { +				result[i] = 0; +			} +			for (size_t j = 0; j < height; ++j) { +				result[j*width] = 0; +			} + +			// Compute the LCS lengths for subsets +			Predicate predicate; +			for (size_t i = 1; i < width; ++i) { +				for (size_t j = 1; j < height; ++j) { +					result[i + j*width] = (predicate(*(xBegin + i-1), *(yBegin + j-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)])); +				} +			} +		} +	} + +	template<typename X, typename InsertRemovePredicate, typename UpdatePredicate> +	void computeIndexDiff(const std::vector<X>& x, const std::vector<X>& y, std::vector<size_t>& updates, std::vector<size_t>& postUpdates, std::vector<size_t>& removes, std::vector<size_t>& inserts) { +		InsertRemovePredicate insertRemovePredicate; +		UpdatePredicate updatePredicate; + +		// Find & handle common prefix (Optimization to reduce LCS matrix size) +		typename std::vector<X>::const_iterator xBegin = x.begin(); +		typename std::vector<X>::const_iterator yBegin = y.begin(); +		while (xBegin < x.end() && yBegin < y.end() && insertRemovePredicate(*xBegin, *yBegin)) { +			if (updatePredicate(*xBegin, *yBegin)) { +				updates.push_back(std::distance(x.begin(), xBegin)); +				postUpdates.push_back(std::distance(y.begin(), yBegin)); +			} +			++xBegin; +			++yBegin; +		} +		size_t prefixLength = std::distance(x.begin(), xBegin); + +		// Find & handle common suffix (Optimization to reduce LCS matrix size) +		typename std::vector<X>::const_reverse_iterator xEnd = x.rbegin(); +		typename std::vector<X>::const_reverse_iterator yEnd = y.rbegin(); +		while (xEnd.base() > xBegin && yEnd.base() > yBegin && insertRemovePredicate(*xEnd, *yEnd)) { +			if (updatePredicate(*xEnd, *yEnd)) { +				updates.push_back(std::distance(x.begin(), xEnd.base()) - 1); +				postUpdates.push_back(std::distance(y.begin(), yEnd.base()) - 1); +			} +			++xEnd; +			++yEnd; +		} + +		// Compute lengths +		size_t xLength = std::distance(xBegin, xEnd.base()); +		size_t yLength = std::distance(yBegin, yEnd.base()); + +		// Compute LCS matrix +		std::vector<unsigned int> lcs; +		Detail::computeLeastCommonSubsequenceMatrix<typename std::vector<X>::const_iterator, typename std::vector<X>::const_iterator, unsigned int, InsertRemovePredicate>(xBegin, xEnd.base(), yBegin, yEnd.base(), lcs); + +		// Process LCS matrix +		size_t i = xLength; +		size_t j = yLength; +		const size_t width = xLength + 1; +		while (true) { +			if (i > 0 && j > 0 && insertRemovePredicate(x[prefixLength + i-1], y[prefixLength + j-1])) { +				// x[i-1] same +				if (updatePredicate(x[prefixLength + i - 1], y[prefixLength + j - 1])) { +					updates.push_back(prefixLength + i-1); +					postUpdates.push_back(prefixLength + j-1); +				} +				i -= 1; +				j -= 1; +			} +			else if (j > 0 && (i == 0 || lcs[i + (j-1)*width] >= lcs[i-1 + j*width])) { +				// y[j-1] added +				inserts.push_back(prefixLength + j-1); +				j -= 1; +			} +			else if (i > 0 && (j == 0 || lcs[i + (j-1)*width] < lcs[i-1 + j*width])) { +				// x[i-1] removed +				removes.push_back(prefixLength + i-1); +				i -= 1; +			} +			else { +				break; +			} +		} +	} +} diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index 4e34105..a62a18a 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -107,7 +107,7 @@ void Roster::removeAll() {  void Roster::removeContact(const JID& jid) {  	std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()];  	items->erase(std::remove_if(items->begin(), items->end(), JIDEqualsTo(jid)), items->end()); -	if (items->size() == 0) { +	if (items->empty()) {  		itemMap_.erase(fullJIDMapping_ ? jid : jid.toBare());  	}  	//Causes the delete @@ -124,7 +124,7 @@ void Roster::removeContactFromGroup(const JID& jid, const std::string& groupName  			std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()];  			items->erase(std::remove(items->begin(), items->end(), deleted), items->end());  		} -		it++; +		++it;  	}  	foreach (ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) {  		item->removeGroup(groupName); @@ -179,7 +179,7 @@ void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) {  	foreach (RosterFilter *filter, filters_) {  		hide &= (*filter)(contact);  	} -	group->setDisplayed(contact, filters_.size() == 0 || !hide); +	group->setDisplayed(contact, filters_.empty() || !hide);  	int newDisplayedSize = group->getDisplayedChildren().size();  	if (oldDisplayedSize == 0 && newDisplayedSize > 0) {  		onGroupAdded(group); diff --git a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp index c1045ee..0a242ae 100644 --- a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp +++ b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp @@ -9,6 +9,7 @@  #include <boost/bind.hpp>  #include <vector> +#include <Swiften/Base/foreach.h>  #include "Swiften/Base/String.h"  #include "Swift/Controllers/Roster/GroupRosterItem.h" diff --git a/Swift/Controllers/Roster/RosterItem.cpp b/Swift/Controllers/Roster/RosterItem.cpp index 3f130bb..77db8a3 100644 --- a/Swift/Controllers/Roster/RosterItem.cpp +++ b/Swift/Controllers/Roster/RosterItem.cpp @@ -33,11 +33,11 @@ void RosterItem::setDisplayName(const std::string& name) {  	onDataChanged();  } -std::string RosterItem::getDisplayName() const { +const std::string& RosterItem::getDisplayName() const {  	return name_;  } -std::string RosterItem::getSortableDisplayName() const { +const std::string& RosterItem::getSortableDisplayName() const {  	return sortableDisplayName_;  } diff --git a/Swift/Controllers/Roster/RosterItem.h b/Swift/Controllers/Roster/RosterItem.h index ed8cb16..769f72d 100644 --- a/Swift/Controllers/Roster/RosterItem.h +++ b/Swift/Controllers/Roster/RosterItem.h @@ -20,8 +20,8 @@ class RosterItem {  		boost::signal<void ()> onDataChanged;  		GroupRosterItem* getParent() const;  		void setDisplayName(const std::string& name); -		std::string getDisplayName() const; -		std::string getSortableDisplayName() const; +		const std::string& getDisplayName() const; +		virtual const std::string& getSortableDisplayName() const;  	private:  		std::string name_;  		std::string sortableDisplayName_; diff --git a/Swift/Controllers/Roster/TableRoster.cpp b/Swift/Controllers/Roster/TableRoster.cpp new file mode 100644 index 0000000..c00bf4f --- /dev/null +++ b/Swift/Controllers/Roster/TableRoster.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Roster/TableRoster.h> + +#include <boost/cast.hpp> +#include <cassert> +#include <algorithm> +#include <Swiften/Base/foreach.h> + +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Network/Timer.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/LeastCommonSubsequence.h> + +namespace Swift { +	struct SectionNameEquals { +		bool operator()(const TableRoster::Section& s1, const TableRoster::Section& s2) const { +			return s1.name == s2.name; +		} +	}; + +	template<typename T> +	struct True { +		bool operator()(const T&, const T&) const { +			return true; +		} +	}; + +	struct ItemEquals { +			bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const { +				return i1.jid == i2.jid; +			} +	}; + + +	struct ItemNeedsUpdate { +			bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const { +				return i1.status != i2.status || i1.description != i2.description || i1.name != i2.name || i1.avatarPath.empty() != i2.avatarPath.empty(); +			} +	}; + +	struct CreateIndexForSection { +			CreateIndexForSection(size_t section) : section(section) { +			} + +			TableRoster::Index operator()(size_t row) const { +				return TableRoster::Index(section, row); +			} + +			size_t section; +	}; +} + +using namespace Swift; + +TableRoster::TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay) : model(model), updatePending(false) { +	updateTimer = timerFactory->createTimer(updateDelay); +	updateTimer->onTick.connect(boost::bind(&TableRoster::handleUpdateTimerTick, this)); +	if (model) { +		model->onChildrenChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this)); +		model->onGroupAdded.connect(boost::bind(&TableRoster::scheduleUpdate, this)); +		model->onDataChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this)); +	} +} + +TableRoster::~TableRoster() { +	updateTimer->stop(); +	updateTimer->onTick.disconnect(boost::bind(&TableRoster::handleUpdateTimerTick, this)); +	if (model) { +		model->onDataChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); +		model->onGroupAdded.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); +		model->onChildrenChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); +	} +} +			 +size_t TableRoster::getNumberOfSections() const { +	return sections.size(); +} + +const std::string& TableRoster::getSectionTitle(size_t section) { +	return sections[section].name; +} + +size_t TableRoster::getNumberOfRowsInSection(size_t section) const { +	return sections[section].items.size(); +} + +const TableRoster::Item& TableRoster::getItem(const Index& index) const { +	return sections[index.section].items[index.row]; +} +			 +void TableRoster::handleUpdateTimerTick() { +	updateTimer->stop(); +	updatePending = false; + +	// Get a model for the new roster +	std::vector<Section> newSections; +	if (model) { +		foreach(RosterItem* item, model->getRoot()->getDisplayedChildren()) { +			if (GroupRosterItem* groupItem = boost::polymorphic_downcast<GroupRosterItem*>(item)) { +				//std::cerr << "* " << groupItem->getDisplayName() << std::endl; +				Section section(groupItem->getDisplayName()); +				foreach(RosterItem* groupChildItem, groupItem->getDisplayedChildren()) { +					if (ContactRosterItem* contact = boost::polymorphic_downcast<ContactRosterItem*>(groupChildItem)) { +						//std::cerr << "  - " << contact->getDisplayJID() << std::endl; +						section.items.push_back(Item(contact->getDisplayName(), contact->getStatusText(), contact->getDisplayJID(), contact->getStatusShow(), contact->getAvatarPath())); +					} +				} +				newSections.push_back(section); +			} +		} +	} + +	// Do a diff with the previous roster +	Update update; +	std::vector<size_t> sectionUpdates; +	std::vector<size_t> sectionPostUpdates; +	computeIndexDiff<Section,SectionNameEquals,True<Section> >(sections, newSections, sectionUpdates, sectionPostUpdates, update.deletedSections, update.insertedSections); +	assert(sectionUpdates.size() == sectionPostUpdates.size()); +	for (size_t i = 0; i < sectionUpdates.size(); ++i) { +		assert(sectionUpdates[i] < sections.size()); +		assert(sectionPostUpdates[i] < newSections.size()); +		std::vector<size_t> itemUpdates; +		std::vector<size_t> itemPostUpdates; +		std::vector<size_t> itemRemoves; +		std::vector<size_t> itemInserts; +		computeIndexDiff<Item, ItemEquals, ItemNeedsUpdate >(sections[sectionUpdates[i]].items, newSections[sectionPostUpdates[i]].items, itemUpdates, itemPostUpdates, itemRemoves, itemInserts); +		size_t end = update.insertedRows.size(); +		update.insertedRows.resize(update.insertedRows.size() + itemInserts.size()); +		std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); +		end = update.deletedRows.size(); +		update.deletedRows.resize(update.deletedRows.size() + itemRemoves.size()); +		std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + end, CreateIndexForSection(sectionUpdates[i])); +		end = update.updatedRows.size(); +		update.updatedRows.resize(update.updatedRows.size() + itemUpdates.size()); +		std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); +	} +	 +	// Switch the old model with the new +	sections.swap(newSections); + +	/* +	std::cerr << "-S: "; +	for (size_t i = 0; i < update.deletedSections.size(); ++i) { +		std::cerr << update.deletedSections[i] << " "; +	} +	std::cerr << std::endl; +	std::cerr << "+S: "; +	for (size_t i = 0; i < update.insertedSections.size(); ++i) { +		std::cerr << update.insertedSections[i] << " "; +	} +	std::cerr << std::endl; +	std::cerr << "-R: "; +	for (size_t i = 0; i < update.deletedRows.size(); ++i) { +		std::cerr << update.deletedRows[i].section << "," << update.deletedRows[i].row << " "; +	} +	std::cerr << std::endl; +	std::cerr << "*R: "; +	for (size_t i = 0; i < update.updatedRows.size(); ++i) { +		std::cerr << update.updatedRows[i].section << "," << update.updatedRows[i].row << " "; +	} +	std::cerr << std::endl; +	std::cerr << "+R: "; +	for (size_t i = 0; i < update.insertedRows.size(); ++i) { +		std::cerr << update.insertedRows[i].section << "," << update.insertedRows[i].row << " "; +	} +	std::cerr << std::endl; +	*/ + +	// Emit the update +	onUpdate(update); +} + +void TableRoster::scheduleUpdate() { +	if (!updatePending) { +		updatePending = true; +		updateTimer->start(); +	} +} diff --git a/Swift/Controllers/Roster/TableRoster.h b/Swift/Controllers/Roster/TableRoster.h new file mode 100644 index 0000000..8ff16d0 --- /dev/null +++ b/Swift/Controllers/Roster/TableRoster.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StatusShow.h> + +namespace Swift { +	class Roster; +	class TimerFactory; +	class Timer; + +	class TableRoster { +		public: +			struct Item { +				Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const std::string& avatarPath) : name(name), description(description), jid(jid), status(status), avatarPath(avatarPath) { +				} +				std::string name; +				std::string description; +				JID jid; +				StatusShow::Type status; +				std::string avatarPath; +			}; + +			struct Index { +				Index(size_t section = 0, size_t row = 0) : section(section), row(row) { +				} +				size_t section; +				size_t row; + +				bool operator==(const Index& o) const { +					return o.section == section && o.row == row; +				} +			}; + +			struct Update { +				std::vector<Index> updatedRows; +				std::vector<Index> insertedRows; +				std::vector<Index> deletedRows; +				std::vector<size_t> insertedSections; +				std::vector<size_t> deletedSections; +			}; + +			TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay); +			~TableRoster(); +			 +			size_t getNumberOfSections() const; +			size_t getNumberOfRowsInSection(size_t section) const; + +			const std::string& getSectionTitle(size_t); + +			const Item& getItem(const Index&) const; + +			boost::signal<void (const Update&)> onUpdate; + +		private: +			void handleUpdateTimerTick(); +			void scheduleUpdate(); + +		private: +			friend class SectionNameEquals; +			struct Section { +				Section(const std::string& name) : name(name) { +				} + +				std::string name; +				std::vector<Item> items; +			}; + +			Roster* model; +			std::vector<Section> sections; +			bool updatePending; +			boost::shared_ptr<Timer> updateTimer; +	}; +} diff --git a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp new file mode 100644 index 0000000..3acab12 --- /dev/null +++ b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/assign/list_of.hpp> +#include <functional> + +#include <QA/Checker/IO.h> +#include <Swift/Controllers/Roster/LeastCommonSubsequence.h> + +using namespace Swift; + +struct IsBOrC { +	bool operator()(char c, char c2) const { +		CPPUNIT_ASSERT_EQUAL(c, c2); +		return c == 'b' || c == 'c'; +	} +}; + +struct IsXOrY { +	bool operator()(char c, char c2) const { +		CPPUNIT_ASSERT_EQUAL(c, c2); +		return c == 'x' || c == 'y'; +	} +}; + +struct IsArizonaOrNewJersey { +	bool operator()(const std::string& s, const std::string& s2) const { +		CPPUNIT_ASSERT_EQUAL(s, s2); +		return s == "Arizona" || s == "New Jersey"; +	} +}; + +class LeastCommonSubsequenceTest : public CppUnit::TestFixture { +		CPPUNIT_TEST_SUITE(LeastCommonSubsequenceTest); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_1); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_2); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence1Empty); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence2Empty); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_NoCommonSequence); +		CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_SameSequences); +		CPPUNIT_TEST(testComputeIndexDiff_1); +		CPPUNIT_TEST(testComputeIndexDiff_2); +		CPPUNIT_TEST(testComputeIndexDiff_Sequence1Empty); +		CPPUNIT_TEST(testComputeIndexDiff_Sequence2Empty); +		CPPUNIT_TEST(testComputeIndexDiff_BothSequencesEmpty); +		CPPUNIT_TEST(testComputeIndexDiff_NoCommonSequence); +		CPPUNIT_TEST(testComputeIndexDiff_SameSequences); +		CPPUNIT_TEST(testComputeIndexDiff_CommonPrefixAndSuffix); +		CPPUNIT_TEST_SUITE_END(); + +	public: +		void testComputeLeastCommonSubsequenceMatrix_1() { +			std::vector<char> x = boost::assign::list_of('x')('m')('j')('y')('a')('u')('z'); +			std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u'); + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0)(0)(0)(0)(0)(0)(0)(0) +				(0)(0)(1)(1)(1)(1)(1)(1) +				(0)(0)(1)(1)(1)(1)(1)(2) +				(0)(0)(1)(2)(2)(2)(2)(2) +				(0)(0)(1)(2)(2)(3)(3)(3) +				(0)(0)(1)(2)(2)(3)(3)(3) +				(0)(1)(1)(2)(2)(3)(3)(3) +				(0)(1)(1)(2)(2)(3)(4)(4); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_2() { +			std::vector<char> x = boost::assign::list_of('x')('x')('x')('m')('j')('y')('a')('u')('z'); +			std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u'); + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0)(0)(0)(0)(0)(0)(0)(0)(0)(0) +				(0)(0)(0)(0)(1)(1)(1)(1)(1)(1) +				(0)(0)(0)(0)(1)(1)(1)(1)(1)(2) +				(0)(0)(0)(0)(1)(2)(2)(2)(2)(2) +				(0)(0)(0)(0)(1)(2)(2)(3)(3)(3) +				(0)(0)(0)(0)(1)(2)(2)(3)(3)(3) +				(0)(1)(1)(1)(1)(2)(2)(3)(3)(3) +				(0)(1)(1)(1)(1)(2)(2)(3)(4)(4); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_Sequence1Empty() { +			std::vector<char> x; +			std::vector<char> y = boost::assign::list_of('a')('b')('c'); + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0) +				(0) +				(0) +				(0); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_Sequence2Empty() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y; + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0)(0)(0)(0); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty() { +			std::vector<char> x; +			std::vector<char> y; + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of(0); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_NoCommonSequence() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y = boost::assign::list_of('d')('e')('f')('g'); + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0)(0)(0)(0) +				(0)(0)(0)(0) +				(0)(0)(0)(0) +				(0)(0)(0)(0) +				(0)(0)(0)(0); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeLeastCommonSubsequenceMatrix_SameSequences() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y = boost::assign::list_of('a')('b')('c'); + +			std::vector<int> result; +			Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + +			std::vector<int> expected = boost::assign::list_of +				(0)(0)(0)(0) +				(0)(1)(1)(1) +				(0)(1)(2)(2) +				(0)(1)(2)(3); +			CPPUNIT_ASSERT_EQUAL(expected, result); +		} + +		void testComputeIndexDiff_1() { +			std::vector<std::string> x = boost::assign::list_of("Arizona")("California")("Delaware")("New Jersey")("Washington"); +			std::vector<std::string> y = boost::assign::list_of("Alaska")("Arizona")("California")("Georgia")("New Jersey")("Virginia"); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<std::string, std::equal_to<std::string>, IsArizonaOrNewJersey >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedUpdates = boost::assign::list_of(3)(0); +			std::vector<size_t> expectedPostUpdates = boost::assign::list_of(4)(1); +			std::vector<size_t> expectedRemoves = boost::assign::list_of(4)(2); +			std::vector<size_t> expectedInserts = boost::assign::list_of(5)(3)(0); +			CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); +			CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates); +			CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); +			CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); +		} + +		void testComputeIndexDiff_2() { +			std::vector<char> x = boost::assign::list_of('x')('y'); +			std::vector<char> y = boost::assign::list_of('x'); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedRemoves = boost::assign::list_of(1); +			CPPUNIT_ASSERT(updates.empty()); +			CPPUNIT_ASSERT(postUpdates.empty()); +			CPPUNIT_ASSERT(inserts.empty()); +			CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); +		} + +		void testComputeIndexDiff_Sequence1Empty() { +			std::vector<char> x; +			std::vector<char> y = boost::assign::list_of('a')('b')('c'); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedInserts = boost::assign::list_of(2)(1)(0); +			CPPUNIT_ASSERT(updates.empty()); +			CPPUNIT_ASSERT(postUpdates.empty()); +			CPPUNIT_ASSERT(removes.empty()); +			CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); +		} + +		void testComputeIndexDiff_Sequence2Empty() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y; + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0); +			CPPUNIT_ASSERT(updates.empty()); +			CPPUNIT_ASSERT(postUpdates.empty()); +			CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); +			CPPUNIT_ASSERT(inserts.empty()); +		} + +		void testComputeIndexDiff_BothSequencesEmpty() { +			std::vector<char> x; +			std::vector<char> y; + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			CPPUNIT_ASSERT(updates.empty()); +			CPPUNIT_ASSERT(postUpdates.empty()); +			CPPUNIT_ASSERT(removes.empty()); +			CPPUNIT_ASSERT(inserts.empty()); +		} + +		void testComputeIndexDiff_NoCommonSequence() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y = boost::assign::list_of('d')('e')('f')('g'); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0); +			std::vector<size_t> expectedInserts = boost::assign::list_of(3)(2)(1)(0); +			CPPUNIT_ASSERT(updates.empty()); +			CPPUNIT_ASSERT(postUpdates.empty()); +			CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); +			CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); +		} + +		void testComputeIndexDiff_SameSequences() { +			std::vector<char> x = boost::assign::list_of('a')('b')('c'); +			std::vector<char> y = boost::assign::list_of('a')('b')('c'); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedUpdates = boost::assign::list_of(1)(2); +			CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); +			CPPUNIT_ASSERT_EQUAL(expectedUpdates, postUpdates); +			CPPUNIT_ASSERT(removes.empty()); +			CPPUNIT_ASSERT(inserts.empty()); +		} + +		void testComputeIndexDiff_CommonPrefixAndSuffix() { +			std::vector<char> x = boost::assign::list_of('x')('x')('x')('x')('a')('b')('c')('d')('e')('y')('y')('y'); +			std::vector<char> y = boost::assign::list_of('x')('x')('x')('x')('e')('a')('b')('f')('d')('g')('y')('y')('y'); + +			std::vector<size_t> updates; +			std::vector<size_t> postUpdates; +			std::vector<size_t> removes; +			std::vector<size_t> inserts; +			computeIndexDiff<char, std::equal_to<char>, IsXOrY >(x, y, updates, postUpdates, removes, inserts); +			 +			std::vector<size_t> expectedUpdates = boost::assign::list_of(0)(1)(2)(3)(11)(10)(9); +			std::vector<size_t> expectedPostUpdates = boost::assign::list_of(0)(1)(2)(3)(12)(11)(10); +			std::vector<size_t> expectedRemoves = boost::assign::list_of(8)(6); +			std::vector<size_t> expectedInserts = boost::assign::list_of(9)(7)(4); +			CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); +			CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates); +			CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); +			CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); +		} +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LeastCommonSubsequenceTest); diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index 16f2745..ca74dbb 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -8,6 +8,7 @@  #include <cppunit/extensions/HelperMacros.h>  #include <cppunit/extensions/TestFactoryRegistry.h> +#include <Swiften/Base/foreach.h>  #include "Swift/Controllers/Roster/RosterController.h"  #include "Swift/Controllers/UnitTest/MockMainWindowFactory.h"  // #include "Swiften/Elements/Payload.h" diff --git a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp index cbef787..4444e8a 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp @@ -21,6 +21,7 @@ class RosterTest : public CppUnit::TestFixture {  		CPPUNIT_TEST(testRemoveSecondContact);  		CPPUNIT_TEST(testRemoveSecondContactSameBare);  		CPPUNIT_TEST(testApplyPresenceLikeMUC); +		CPPUNIT_TEST(testReSortLikeMUC);  		CPPUNIT_TEST_SUITE_END();  	public: @@ -117,6 +118,22 @@ class RosterTest : public CppUnit::TestFixture {  		} +		void testReSortLikeMUC() { +			JID jid4a("a@b/c"); +			JID jid4b("a@b/d"); +			JID jid4c("a@b/e"); +			roster_->addContact(jid4a, JID(), "Bird", "group1", ""); +			roster_->addContact(jid4b, JID(), "Cookie", "group2", ""); +			roster_->addContact(jid4b, JID(), "Ernie", "group1", ""); +			roster_->getGroup("group1")->setManualSort("2"); +			roster_->getGroup("group2")->setManualSort("1"); +			GroupRosterItem* root = roster_->getRoot(); +			const std::vector<RosterItem*> kids = root->getDisplayedChildren(); +			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), kids.size()); +			CPPUNIT_ASSERT_EQUAL(std::string("group2"), kids[0]->getDisplayName()); +			CPPUNIT_ASSERT_EQUAL(std::string("group1"), kids[1]->getDisplayName()); +		} +  	private:  		Roster *roster_;  		JID jid1_; diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp new file mode 100644 index 0000000..e433b50 --- /dev/null +++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Roster/TableRoster.h> + +std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { +	os << "(" << i.section << ", " << i.row << ")"; +	return os; +} + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/variant.hpp> + +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/SetPresence.h> + +using namespace Swift; + +class TableRosterTest : public CppUnit::TestFixture { +		CPPUNIT_TEST_SUITE(TableRosterTest); +		CPPUNIT_TEST(testAddContact_EmptyRoster); +		CPPUNIT_TEST_SUITE_END(); + +	public: +		void setUp() { +			timerFactory = new DummyTimerFactory(); +			roster = new Roster(); +			jid1 = JID("jid1@example.com"); +			jid2 = JID("jid2@example.com"); +		} + +		void tearDown() { +			delete roster; +			delete timerFactory; +		} + +		void testAddContact_EmptyRoster() { +			/* +			boost::shared_ptr<TableRoster> tableRoster(createTestling()); + +			addContact(jid1, "1", "group1"); + +			CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(events.size())); +			CPPUNIT_ASSERT(boost::get<BeginUpdatesEvent>(&events[0])); +			CPPUNIT_ASSERT(boost::get<SectionsInsertedEvent>(&events[1])); +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections.size())); +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections[0])); +			CPPUNIT_ASSERT(boost::get<RowsInsertedEvent>(&events[2])); +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<RowsInsertedEvent>(events[2]).rows.size())); +			CPPUNIT_ASSERT_EQUAL(TableRoster::Index(0, 0), boost::get<RowsInsertedEvent>(events[2]).rows[0]); +			CPPUNIT_ASSERT(boost::get<EndUpdatesEvent>(&events[3])); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfSections())); +			CPPUNIT_ASSERT_EQUAL(std::string("group1"), tableRoster->getSectionTitle(0)); +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfRowsInSection(0))); +			CPPUNIT_ASSERT_EQUAL(jid1, tableRoster->getItem(TableRoster::Index(0, 0)).jid); +			*/ +		} + +	private: +		void addContact(const JID& jid, const std::string& name, const std::string& group) { +			roster->addContact(jid, JID(), name, group, ""); +		} + +		TableRoster* createTestling() { +			TableRoster* result = new TableRoster(roster, timerFactory, 10); +			result->onUpdate.connect(boost::bind(&TableRosterTest::handleUpdate, this, _1)); +			return result; +		} + +		void handleUpdate(const TableRoster::Update& update) { +			updates.push_back(update); +		} + +	private: +		DummyTimerFactory* timerFactory; +		Roster* roster; +		JID jid1; +		JID jid2; +		std::vector<TableRoster::Update> updates; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TableRosterTest); + | 
 Swift
 Swift