From 3f8ff1c0e154dcb9959906e6865053dbe975892f Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Sun, 10 Nov 2013 15:56:22 +0000
Subject: Improve tooltips to include avatars, last seen and vcard information.

Change-Id: I3768d9891ba903c5e2ce8217de0b4413ce40bb9a
License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 5d69019..a7e8b73 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -30,6 +30,7 @@
 #include <Swiften/Roster/XMPPRoster.h>
 #include <Swiften/Client/ClientBlockListManager.h>
 #include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/VCards/VCardManager.h>
 
 #include <Swift/Controllers/Chat/ChatController.h>
 #include <Swift/Controllers/Chat/ChatControllerBase.h>
@@ -123,7 +124,8 @@ ChatsManager::ChatsManager(
 		HighlightManager* highlightManager,
 		ClientBlockListManager* clientBlockListManager,
 		const std::map<std::string, std::string>& emoticons,
-		UserSearchController* inviteUserSearchController) :
+		UserSearchController* inviteUserSearchController,
+		VCardManager* vcardManager) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -138,7 +140,8 @@ ChatsManager::ChatsManager(
 			whiteboardManager_(whiteboardManager),
 			highlightManager_(highlightManager),
 			clientBlockListManager_(clientBlockListManager),
-			inviteUserSearchController_(inviteUserSearchController) {
+			inviteUserSearchController_(inviteUserSearchController),
+			vcardManager_(vcardManager) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -767,7 +770,7 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti
 		if (reuseChatwindow) {
 			chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
 		}
-		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_);
+		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);
 		if (chatWindowFactoryAdapter) {
 			/* The adapters are only passed to chat windows, which are deleted in their
 			 * controllers' dtor, which are deleted in ChatManager's dtor. The adapters
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index c9dd856..979d52a 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -62,10 +62,11 @@ namespace Swift {
 	class DiscoServiceWalker;
 	class AutoAcceptMUCInviteDecider;
 	class UserSearchController;
+	class VCardManager;
 
 	class ChatsManager : public ContactProvider {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController, VCardManager* vcardManager);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -180,5 +181,6 @@ namespace Swift {
 			AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
 			UserSearchController* inviteUserSearchController_;
 			IDGenerator idGenerator_;
+			VCardManager* vcardManager_;
 	};
 }
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 37631a5..ed3ed5f 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -33,6 +33,7 @@
 #include <Swiften/MUC/MUC.h>
 #include <Swiften/Client/StanzaChannel.h>
 #include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/RosterVCardProvider.h>
 #include <Swift/Controllers/Roster/SetAvatar.h>
 #include <Swift/Controllers/Roster/SetPresence.h>
 #include <Swiften/Disco/EntityCapsProvider.h>
@@ -70,7 +71,8 @@ MUCController::MUCController (
 		HighlightManager* highlightManager,
 		ChatMessageParser* chatMessageParser,
 		bool isImpromptu,
-		AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) :
+		AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
+		VCardManager* vcardManager) :
 	ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) {
 	parting_ = true;
 	joined_ = false;
@@ -81,6 +83,7 @@ MUCController::MUCController (
 	xmppRoster_ = roster;
 	
 	roster_ = new Roster(false, true);
+	rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource);
 	completer_ = new TabComplete();
 	chatWindow_->setRosterModel(roster_);
 	chatWindow_->setTabComplete(completer_);
@@ -130,6 +133,7 @@ MUCController::MUCController (
 MUCController::~MUCController() {
 	eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
 	chatWindow_->setRosterModel(NULL);
+	delete rosterVCardProvider_;
 	delete roster_;
 	if (loginCheckTimer_) {
 		loginCheckTimer_->stop();
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 9283438..317f579 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -37,6 +37,8 @@ namespace Swift {
 	class XMPPRoster;
 	class HighlightManager;
 	class UIEvent;
+	class VCardManager;
+	class RosterVCardProvider;
 
 	enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
 
@@ -48,8 +50,8 @@ namespace Swift {
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
-			~MUCController();
+			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
+			virtual ~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
 			boost::signal<void ()> onImpromptuConfigCompleted;
@@ -146,6 +148,7 @@ namespace Swift {
 			size_t renameCounter_;
 			bool isImpromptu_;
 			bool isImpromptuAlreadyConfigured_;
+			RosterVCardProvider* rosterVCardProvider_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index f5a3003..4c604ac 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -6,53 +6,58 @@
 
 #include <cppunit/extensions/HelperMacros.h>
 #include <cppunit/extensions/TestFactoryRegistry.h>
+
 #include <hippomocks.h>
 
 #include <boost/bind.hpp>
 
-#include <Swift/Controllers/Chat/ChatsManager.h>
-
-#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
-#include <Swift/Controllers/Settings/DummySettingsProvider.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
+#include <Swiften/Avatars/AvatarMemoryStorage.h>
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Base/Algorithm.h>
 #include <Swiften/Client/Client.h>
-#include <Swiften/Disco/EntityCapsManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swiften/Crypto/PlatformCryptoProvider.h>
 #include <Swiften/Disco/CapsProvider.h>
+#include <Swiften/Disco/EntityCapsManager.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
+#include <Swiften/Jingle/JingleSessionManager.h>
 #include <Swiften/MUC/MUCManager.h>
-#include <Swift/Controllers/Chat/ChatController.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
-#include <Swift/Controllers/Chat/MUCController.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Presence/PresenceOracle.h>
 #include <Swiften/Presence/StanzaChannelPresenceSender.h>
-#include <Swiften/Avatars/NullAvatarManager.h>
-#include <Swiften/Avatars/AvatarMemoryStorage.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swiften/VCards/VCardManager.h>
 #include <Swiften/VCards/VCardManager.h>
 #include <Swiften/VCards/VCardMemoryStorage.h>
-#include <Swiften/Client/NickResolver.h>
-#include <Swiften/Presence/DirectedPresenceSender.h>
-#include <Swiften/Roster/XMPPRosterImpl.h>
-#include <Swift/Controllers/UnitTest/MockChatWindow.h>
-#include <Swiften/Client/DummyStanzaChannel.h>
-#include <Swiften/Queries/DummyIQChannel.h>
-#include <Swiften/Presence/PresenceOracle.h>
-#include <Swiften/Jingle/JingleSessionManager.h>
-#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
-#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
-#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/UIEventStream.h>
-#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+
+#include <Swift/Controllers/Chat/ChatsManager.h>
+#include <Swift/Controllers/Chat/ChatController.h>
+#include <Swift/Controllers/Chat/MUCController.h>
+#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h>
 #include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
-#include <Swiften/Elements/DeliveryReceiptRequest.h>
-#include <Swiften/Elements/DeliveryReceipt.h>
-#include <Swiften/Base/Algorithm.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
 #include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
+#include <Swift/Controllers/UnitTest/MockChatWindow.h>
 #include <Swift/Controllers/WhiteboardManager.h>
-#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
-#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+
 
 using namespace Swift;
 
@@ -109,9 +114,12 @@ public:
 		wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
 		highlightManager_ = new HighlightManager(settings_);
 
+		crypto_ = PlatformCryptoProvider::create();
+		vcardStorage_ = new VCardMemoryStorage(crypto_);
+		vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_);
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
 		clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL, vcardManager_);
 
 		manager_->setAvatarManager(avatarManager_);
 	}
@@ -123,6 +131,9 @@ public:
 		delete avatarManager_;
 		delete manager_;
 		delete clientBlockListManager_;
+		delete vcardManager_;
+		delete vcardStorage_;
+		delete crypto_;
 		delete ftOverview_;
 		delete ftManager_;
 		delete wbSessionManager_;
@@ -488,6 +499,9 @@ private:
 	WhiteboardManager* wbManager_;
 	HighlightManager* highlightManager_;
 	ClientBlockListManager* clientBlockListManager_;
+	VCardManager* vcardManager_;
+	CryptoProvider* crypto_;
+	VCardStorage* vcardStorage_;
 	std::map<std::string, std::string> emoticons_;
 };
 
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5ca0687..291fb22 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -79,16 +79,16 @@ public:
 		chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
 		vcardStorage_ = new VCardMemoryStorage(crypto_.get());
 		vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL);
+		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_);
 	}
 
 	void tearDown() {
+		delete controller_;
 		delete vcardManager_;
 		delete vcardStorage_;
 		delete highlightManager_;
 		delete settings_;
 		delete entityCapsProvider_;
-		delete controller_;
 		delete eventController_;
 		delete presenceOracle_;
 		delete mocks_;
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index a201994..45a0df7 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -12,7 +12,7 @@
 
 #include <Swift/Controllers/MainController.h>
 
-#include <stdlib.h>
+#include <cstdlib>
 
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
@@ -91,7 +91,6 @@
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/HighlightEditorController.h>
 #include <Swift/Controllers/BlockListController.h>
-#include <Swiften/Crypto/CryptoProvider.h>
 #include <Swift/Controllers/ContactSuggester.h>
 #include <Swift/Controllers/ContactsFromXMPPRoster.h>
 
@@ -340,7 +339,7 @@ void MainController::handleConnected() {
 		showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_);
 		ftOverview_ = new FileTransferOverview(client_->getFileTransferManager());
 		fileTransferListController_->setFileTransferOverview(ftOverview_);
-		rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager());
+		rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager());
 		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
 		rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this));
 		rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this));
@@ -362,9 +361,9 @@ void MainController::handleConnected() {
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 		historyController_ = new HistoryController(storages_->getHistoryStorage());
 		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager());
 #else
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager());
 #endif
 		contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle());
 		contactSuggesterWithoutRoster_->addContactProvider(chatsManager_);
diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp
index 70b4a1b..6239033 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.cpp
+++ b/Swift/Controllers/Roster/ContactRosterItem.cpp
@@ -4,14 +4,15 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Roster/ContactRosterItem.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
 
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Base/DateTime.h>
 #include <Swiften/Elements/Idle.h>
 
-#include <boost/date_time/posix_time/posix_time.hpp>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
 
 namespace Swift {
 
@@ -52,6 +53,16 @@ std::string ContactRosterItem::getIdleText() const {
 	}
 }
 
+std::string ContactRosterItem::getOfflineSinceText() const {
+	if (offlinePresence_) {
+		boost::optional<boost::posix_time::ptime> delay = offlinePresence_->getTimestamp();
+		if (offlinePresence_->getType() == Presence::Unavailable && delay) {
+			return dateTimeToLocalString(*delay);
+		}
+	}
+	return "";
+}
+
 void ContactRosterItem::setAvatarPath(const boost::filesystem::path& path) {
 	avatarPath_ = path;
 	onDataChanged();
@@ -136,12 +147,20 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const {
 
 void ContactRosterItem::setBlockState(BlockState state) {
 	blockState_ = state;
+	onDataChanged();
 }
 
 ContactRosterItem::BlockState ContactRosterItem::blockState() const {
 	return blockState_;
 }
 
+VCard::ref ContactRosterItem::getVCard() const {
+	return vcard_;
 }
 
+void ContactRosterItem::setVCard(VCard::ref vcard) {
+	vcard_ = vcard;
+	onDataChanged();
+}
 
+}
diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h
index 67a9722..d9ca8af 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.h
+++ b/Swift/Controllers/Roster/ContactRosterItem.h
@@ -6,19 +6,22 @@
 
 #pragma once
 
-#include <string>
-#include "Swiften/JID/JID.h"
-#include "Swift/Controllers/Roster/RosterItem.h"
-#include "Swiften/Elements/StatusShow.h"
-#include "Swiften/Elements/Presence.h"
-
 #include <map>
 #include <set>
+#include <string>
+
 #include <boost/bind.hpp>
-#include "Swiften/Base/boost_bsignals.h"
-#include <boost/shared_ptr.hpp>
 #include <boost/date_time/posix_time/ptime.hpp>
 #include <boost/filesystem/path.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/StatusShow.h>
+#include <Swiften/Elements/VCard.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Roster/RosterItem.h>
 
 namespace Swift {
 
@@ -44,6 +47,7 @@ class ContactRosterItem : public RosterItem {
 		StatusShow::Type getSimplifiedStatusShow() const;
 		std::string getStatusText() const;
 		std::string getIdleText() const;
+		std::string getOfflineSinceText() const;
 		void setAvatarPath(const boost::filesystem::path& path);
 		const boost::filesystem::path& getAvatarPath() const;
 		const JID& getJID() const;
@@ -63,6 +67,11 @@ class ContactRosterItem : public RosterItem {
 		void setBlockState(BlockState state);
 		BlockState blockState() const;
 
+		VCard::ref getVCard() const;
+		void setVCard(VCard::ref vcard);
+
+		boost::signal<void ()> onVCardRequested;
+
 	private:
 		JID jid_;
 		JID displayJID_;
@@ -74,6 +83,7 @@ class ContactRosterItem : public RosterItem {
 		std::vector<std::string> groups_;
 		std::set<Feature> features_;
 		BlockState blockState_;
+		VCard::ref vcard_;
 };
 
 }
diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp
index 9b45b63..dbb1780 100644
--- a/Swift/Controllers/Roster/Roster.cpp
+++ b/Swift/Controllers/Roster/Roster.cpp
@@ -4,22 +4,23 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Roster/Roster.h"
+#include <Swift/Controllers/Roster/Roster.h>
 
-#include "Swiften/Base/foreach.h"
 #include <string>
-#include "Swiften/JID/JID.h"
-#include "Swift/Controllers/Roster/ContactRosterItem.h"
-#include "Swift/Controllers/Roster/RosterItem.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
-#include "Swift/Controllers/Roster/RosterItemOperation.h"
-
-#include <boost/bind.hpp>
-
 #include <iostream>
 #include <set>
 #include <deque>
 
+#include <boost/bind.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/Roster/RosterItem.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/RosterItemOperation.h>
+
 namespace Swift {
 
 Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) {
@@ -39,6 +40,10 @@ Roster::~Roster() {
 		if (group) {
 			queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end());
 		}
+		ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
+		if (contact) {
+			contact->onVCardRequested.disconnect(boost::bind(boost::ref(onVCardUpdateRequested), contact->getJID()));
+		}
 		delete item;
 	}
 }
@@ -107,7 +112,8 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) {
 
 void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const boost::filesystem::path& avatarPath) {
 	GroupRosterItem* group(getGroup(groupName));
-	ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group);	
+	ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group);
+	item->onVCardRequested.connect(boost::bind(boost::ref(onVCardUpdateRequested), jid));
 	item->setAvatarPath(avatarPath);
 	if (blockingSupported_) {
 		item->setBlockState(ContactRosterItem::IsUnblocked);
diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h
index a4c8b99..821c8f5 100644
--- a/Swift/Controllers/Roster/Roster.h
+++ b/Swift/Controllers/Roster/Roster.h
@@ -7,16 +7,18 @@
 #pragma once
 
 #include <string>
-#include "Swiften/JID/JID.h"
-#include "Swift/Controllers/Roster/RosterItemOperation.h"
-#include "Swift/Controllers/Roster/RosterFilter.h"
-#include <Swift/Controllers/Roster/ContactRosterItem.h>
-
 #include <vector>
 #include <map>
-#include "Swiften/Base/boost_bsignals.h"
+
 #include <boost/shared_ptr.hpp>
 
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Roster/RosterItemOperation.h>
+#include <Swift/Controllers/Roster/RosterFilter.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+
 namespace Swift {
 
 class RosterItem;
@@ -43,6 +45,7 @@ class Roster {
 		boost::signal<void (GroupRosterItem*)> onChildrenChanged;
 		boost::signal<void (GroupRosterItem*)> onGroupAdded;
 		boost::signal<void (RosterItem*)> onDataChanged;
+		boost::signal<void (JID&)> onVCardUpdateRequested;
 		GroupRosterItem* getGroup(const std::string& groupName);
 		void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features);
 		void setBlockedState(const std::vector<JID>& jids, ContactRosterItem::BlockState state);
diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp
index d277799..fd0dbb8 100644
--- a/Swift/Controllers/Roster/RosterController.cpp
+++ b/Swift/Controllers/Roster/RosterController.cpp
@@ -4,55 +4,58 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Roster/RosterController.h"
+#include <Swift/Controllers/Roster/RosterController.h>
 
 #include <boost/bind.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
-#include "Swiften/JID/JID.h"
-#include "Swiften/Base/foreach.h"
-#include "Swift/Controllers/UIInterfaces/MainWindow.h"
-#include "Swift/Controllers/UIInterfaces/MainWindowFactory.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swiften/Roster/GetRosterRequest.h"
-#include "Swiften/Roster/SetRosterRequest.h"
-#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h"
-#include "Swift/Controllers/XMPPEvents/ErrorEvent.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Presence/SubscriptionManager.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swiften/Queries/IQRouter.h"
-#include "Swift/Controllers/Roster/Roster.h"
-#include "Swift/Controllers/Roster/SetPresence.h"
-#include "Swift/Controllers/Roster/AppearOffline.h"
-#include "Swift/Controllers/Roster/SetAvatar.h"
-#include "Swift/Controllers/Roster/SetName.h"
-#include "Swift/Controllers/Roster/OfflineRosterFilter.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
-#include "Swiften/Roster/XMPPRoster.h"
-#include "Swiften/Roster/XMPPRosterItem.h"
-#include "Swift/Controllers/UIEvents/AddContactUIEvent.h"
-#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h"
-#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h"
-#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h"
-#include "Swift/Controllers/UIEvents/SendFileUIEvent.h"
-#include <Swiften/FileTransfer/FileTransferManager.h>
-#include <Swiften/Client/NickManager.h>
-#include <Swift/Controllers/Intl.h>
+#include <Swiften/Base/foreach.h>
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Path.h>
-#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/NickManager.h>
+#include <Swiften/Client/NickResolver.h>
 #include <Swiften/Disco/EntityCapsManager.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/FileTransfer/FileTransferManager.h>
+#include <Swiften/JID/JID.h>
 #include <Swiften/Jingle/JingleSessionManager.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Presence/SubscriptionManager.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Roster/GetRosterRequest.h>
+#include <Swiften/Roster/SetRosterRequest.h>
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Roster/XMPPRosterItem.h>
+
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/Roster/AppearOffline.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/OfflineRosterFilter.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/RosterVCardProvider.h>
+#include <Swift/Controllers/Roster/SetAvatar.h>
+#include <Swift/Controllers/Roster/SetName.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
+#include <Swift/Controllers/Roster/SetVCard.h>
 #include <Swift/Controllers/SettingConstants.h>
-#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swift/Controllers/UIEvents/AddContactUIEvent.h>
+#include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h>
+#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+#include <Swift/Controllers/UIInterfaces/MainWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/ErrorEvent.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h>
 
 namespace Swift {
 
 /**
  * The controller does not gain ownership of these parameters.
  */
-RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager)
+RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager)
 	: myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) {
 	assert(fileTransferOverview);
 	iqRouter_ = iqRouter;
@@ -62,6 +65,7 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata
 	settings_ = settings;
 	expandiness_ = new RosterGroupExpandinessPersister(roster_, settings);
 	mainWindow_->setRosterModel(roster_);
+	rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithoutResource);
 	
 	changeStatusConnection_ = mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2));
 	signOutConnection_ = mainWindow_->onSignOutRequest.connect(boost::bind(boost::ref(onSignOutRequest)));
@@ -99,8 +103,8 @@ RosterController::~RosterController() {
 	if (mainWindow_->canDelete()) {
 		delete mainWindow_;
 	}
-	delete roster_;
-	
+	delete rosterVCardProvider_;
+	delete roster_;	
 }
 
 void RosterController::setEnabled(bool enabled) {
diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h
index 06b551e..5b26fc7 100644
--- a/Swift/Controllers/Roster/RosterController.h
+++ b/Swift/Controllers/Roster/RosterController.h
@@ -6,20 +6,23 @@
 
 #pragma once
 
-#include "Swiften/JID/JID.h"
 #include <string>
 #include <set>
-#include "Swiften/Elements/Presence.h"
-#include "Swiften/Elements/ErrorPayload.h"
-#include "Swiften/Elements/RosterPayload.h"
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swift/Controllers/UIEvents/UIEvent.h"
-#include "RosterGroupExpandinessPersister.h"
-#include "Swift/Controllers/FileTransfer/FileTransferOverview.h"
 
-#include "Swiften/Base/boost_bsignals.h"
 #include <boost/shared_ptr.hpp>
 
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Elements/RosterPayload.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/VCards/VCardManager.h>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
+#include <Swift/Controllers/Roster/RosterGroupExpandinessPersister.h>
+
 namespace Swift {
 	class IQRouter;
 	class Roster;
@@ -40,10 +43,11 @@ namespace Swift {
 	class EntityCapsProvider;
 	class FileTransferManager;
 	class ClientBlockListManager;
+	class RosterVCardProvider;
 
 	class RosterController {
 		public:
-			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager);
+			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager);
 			~RosterController();
 			void showRosterWindow();
 			MainWindow* getWindow() {return mainWindow_;}
@@ -102,6 +106,7 @@ namespace Swift {
 			EntityCapsProvider* entityCapsManager_;
 			FileTransferOverview* ftOverview_;
 			ClientBlockListManager* clientBlockListManager_;
+			RosterVCardProvider* rosterVCardProvider_;
 			
 			boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
 			boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
diff --git a/Swift/Controllers/Roster/RosterVCardProvider.cpp b/Swift/Controllers/Roster/RosterVCardProvider.cpp
new file mode 100644
index 0000000..26c9a81
--- /dev/null
+++ b/Swift/Controllers/Roster/RosterVCardProvider.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/Roster/RosterVCardProvider.h>
+
+#include <Swiften/VCards/VCardManager.h>
+
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/SetVCard.h>
+
+namespace Swift {
+
+RosterVCardProvider::RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType) : roster_(roster), vcardManager_(vcardManager), compareType_(compareType) {
+	vcardUpdateRequestedConnection = roster_->onVCardUpdateRequested.connect(boost::bind(&RosterVCardProvider::handleVCardUpdateRequested, this, _1));
+	vcardChangedConnection = vcardManager_->onVCardChanged.connect(boost::bind(&RosterVCardProvider::handleVCardChanged, this, _1, _2));
+}
+
+RosterVCardProvider::~RosterVCardProvider() {
+}
+
+void RosterVCardProvider::handleVCardUpdateRequested(const JID& jid) {
+	VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid);
+	if (vcard) {
+		handleVCardChanged(jid, vcard);
+	}
+}
+
+void RosterVCardProvider::handleVCardChanged(const JID& jid, VCard::ref vcard) {
+	roster_->applyOnItem(SetVCard(jid, vcard, compareType_), jid);
+}
+
+
+}
diff --git a/Swift/Controllers/Roster/RosterVCardProvider.h b/Swift/Controllers/Roster/RosterVCardProvider.h
new file mode 100644
index 0000000..da41298
--- /dev/null
+++ b/Swift/Controllers/Roster/RosterVCardProvider.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/signals/connection.hpp>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Elements/VCard.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+
+class Roster;
+class VCardManager;
+
+class RosterVCardProvider {
+	public:
+		RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType);
+		~RosterVCardProvider();
+
+	private:
+		void handleVCardUpdateRequested(const JID& jid);
+		void handleVCardChanged(const JID& jid, VCard::ref vcard);
+
+	private:
+		Roster* roster_;
+		VCardManager* vcardManager_;
+		JID::CompareType compareType_;
+		boost::bsignals::scoped_connection vcardUpdateRequestedConnection;
+		boost::bsignals::scoped_connection vcardChangedConnection;
+};
+
+}
diff --git a/Swift/Controllers/Roster/SetVCard.h b/Swift/Controllers/Roster/SetVCard.h
new file mode 100644
index 0000000..4a238ff
--- /dev/null
+++ b/Swift/Controllers/Roster/SetVCard.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/VCard.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Roster/RosterItemOperation.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+
+namespace Swift {
+
+class RosterItem;
+
+class SetVCard : public RosterItemOperation {
+	public:
+		SetVCard(const JID& jid, VCard::ref vcard, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), vcard_(vcard), compareType_(compareType) {
+		}
+
+		virtual void operator() (RosterItem* item) const {
+			ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
+			if (contact && contact->getJID().equals(jid_, compareType_)) {
+				contact->setVCard(vcard_);
+			}
+		}
+
+	private:
+		JID jid_;
+		VCard::ref vcard_;
+		JID::CompareType compareType_;
+};
+
+}
diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
index b0034e6..a81d587 100644
--- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
@@ -8,36 +8,40 @@
 #include <cppunit/extensions/HelperMacros.h>
 #include <cppunit/extensions/TestFactoryRegistry.h>
 
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Base/Algorithm.h>
 #include <Swiften/Base/foreach.h>
-#include "Swift/Controllers/Roster/RosterController.h"
-#include "Swift/Controllers/UnitTest/MockMainWindowFactory.h"
-// #include "Swiften/Elements/Payload.h"
-// #include "Swiften/Elements/RosterItemPayload.h"
-// #include "Swiften/Elements/RosterPayload.h"
-#include "Swiften/Queries/DummyIQChannel.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-#include "Swiften/Queries/IQRouter.h"
-#include "Swiften/Roster/XMPPRosterImpl.h"
-#include "Swift/Controllers/Roster/Roster.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
-#include "Swift/Controllers/Roster/ContactRosterItem.h"
-#include "Swift/Controllers/Settings/DummySettingsProvider.h"
-#include "Swiften/Avatars/NullAvatarManager.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Presence/SubscriptionManager.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h"
-#include "Swiften/MUC/MUCRegistry.h"
+#include <Swiften/Client/ClientBlockListManager.h>
 #include <Swiften/Client/DummyNickManager.h>
-#include <Swiften/Disco/EntityCapsManager.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swiften/Crypto/PlatformCryptoProvider.h>
 #include <Swiften/Disco/CapsProvider.h>
-#include <Swiften/Jingle/JingleSessionManager.h>
-#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
-#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Disco/EntityCapsManager.h>
 #include <Swiften/EventLoop/DummyEventLoop.h>
-#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
+#include <Swiften/Jingle/JingleSessionManager.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Presence/SubscriptionManager.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+// #include <Swiften/Elements/Payload.h>
+// #include <Swiften/Elements/RosterItemPayload.h>
+// #include <Swiften/Elements/RosterPayload.h>
+
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/RosterController.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UnitTest/MockMainWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
 
 using namespace Swift;
 
@@ -84,12 +88,18 @@ class RosterControllerTest : public CppUnit::TestFixture {
 			ftManager_ = new DummyFileTransferManager();
 			ftOverview_ = new FileTransferOverview(ftManager_);
 			clientBlockListManager_ = new ClientBlockListManager(router_);
-			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_);
+			crypto_ = PlatformCryptoProvider::create();
+			vcardStorage_ = new VCardMemoryStorage(crypto_);
+			vcardManager_ = new VCardManager(jid_, router_, vcardStorage_);
+			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_, vcardManager_);
 			mainWindow_ = mainWindowFactory_->last;
 		}
 
 		void tearDown() {
 			delete rosterController_;
+			delete vcardManager_;
+			delete vcardStorage_;
+			delete crypto_;
 			delete clientBlockListManager_;
 			delete ftManager_;
 			delete jingleSessionManager_;
@@ -341,6 +351,9 @@ class RosterControllerTest : public CppUnit::TestFixture {
 		FileTransferManager* ftManager_;
 		FileTransferOverview* ftOverview_;
 		ClientBlockListManager* clientBlockListManager_;
+		CryptoProvider* crypto_;
+		VCardStorage* vcardStorage_;
+		VCardManager* vcardManager_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest);
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index ea52084..e0fa508 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -43,6 +43,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Roster/GroupRosterItem.cpp",
 			"Roster/RosterItem.cpp",
 			"Roster/Roster.cpp",
+			"Roster/RosterVCardProvider.cpp",
 			"Roster/TableRoster.cpp",
 			"EventWindowController.cpp",
 			"SoundEventController.cpp",
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index 3b10041..c85106f 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -7,13 +7,15 @@
 #pragma once
 
 #include <string>
-#include "Swiften/JID/JID.h"
-#include "Swiften/Elements/StatusShow.h"
-#include "Swiften/Elements/DiscoItems.h"
-#include "Swiften/TLS/Certificate.h"
-#include "Swiften/Base/boost_bsignals.h"
+
 #include <boost/shared_ptr.hpp>
 
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/StatusShow.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/TLS/Certificate.h>
+#include <Swiften/Base/boost_bsignals.h>
+
 namespace Swift {
 	class Roster;
 
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index 69a4e25..ff3fa4d 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -6,7 +6,7 @@
 
 #pragma once
 
-#include "Swift/Controllers/UIInterfaces/MainWindow.h"
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
 
 namespace Swift {
 	class Roster;
diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
index e9ecec8..5497fdd 100644
--- a/Swift/QtUI/ChatList/ChatListRecentItem.cpp
+++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
@@ -4,10 +4,11 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+#include <Swiften/Base/Path.h>
 
+#include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+#include <Swift/QtUI/QtResourceHelper.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
-#include <Swiften/Base/Path.h>
 
 namespace Swift {
 ChatListRecentItem::ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) {
@@ -33,16 +34,7 @@ QVariant ChatListRecentItem::data(int role) const {
 }
 
 QIcon ChatListRecentItem::getPresenceIcon() const {
-	QString iconString;
-	switch (chat_.statusType) {
-	 	case StatusShow::Online: iconString = "online";break;
-	 	case StatusShow::Away: iconString = "away";break;
-	 	case StatusShow::XA: iconString = "away";break;
-	 	case StatusShow::FFC: iconString = "online";break;
-	 	case StatusShow::DND: iconString = "dnd";break;
-	 	case StatusShow::None: iconString = "offline";break;
-	}
-	return QIcon(":/icons/" + iconString + ".png");
+	return QIcon(statusShowTypeToIconPath(chat_.statusType));
 }
 
 }
diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp
index 6791aa5..05bf6c2 100644
--- a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp
+++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp
@@ -10,10 +10,11 @@
  * See the COPYING file for more information.
  */
 
-#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h>
+#include <Swiften/Base/Path.h>
 
+#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
-#include <Swiften/Base/Path.h>
+#include <Swift/QtUI/QtResourceHelper.h>
 
 namespace Swift {
 	ChatListWhiteboardItem::ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) {
@@ -39,16 +40,7 @@ namespace Swift {
 	}
 
 	QIcon ChatListWhiteboardItem::getPresenceIcon() const {
-		QString iconString;
-		switch (chat_.statusType) {
-	 	case StatusShow::Online: iconString = "online";break;
-	 	case StatusShow::Away: iconString = "away";break;
-	 	case StatusShow::XA: iconString = "away";break;
-	 	case StatusShow::FFC: iconString = "online";break;
-	 	case StatusShow::DND: iconString = "dnd";break;
-	 	case StatusShow::None: iconString = "offline";break;
-		}
-		return QIcon(":/icons/" + iconString + ".png");
+		return QIcon(statusShowTypeToIconPath(chat_.statusType));
 	}
 }
 
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 6f87a88..af38d68 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -4,7 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "QtMainWindow.h"
+#include <Swift/QtUI/QtMainWindow.h>
 
 #include <boost/optional.hpp>
 #include <boost/bind.hpp>
@@ -21,11 +21,8 @@
 #include <QAction>
 #include <QTabWidget>
 
-#include <Swift/QtUI/QtSwiftUtil.h>
-#include <Swift/QtUI/QtTabWidget.h>
-#include <Swift/QtUI/QtSettingsProvider.h>
-#include <Swift/QtUI/QtLoginWindow.h>
-#include <Roster/QtRosterWidget.h>
+#include <Swiften/Base/Platform.h>
+
 #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
@@ -34,10 +31,14 @@
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
-#include <Swift/QtUI/QtUISettingConstants.h>
 #include <Swift/Controllers/SettingConstants.h>
-#include <Swiften/Base/Platform.h>
 
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtTabWidget.h>
+#include <Swift/QtUI/QtSettingsProvider.h>
+#include <Swift/QtUI/QtLoginWindow.h>
+#include <Swift/QtUI/Roster/QtRosterWidget.h>
+#include <Swift/QtUI/QtUISettingConstants.h>
 #if defined(SWIFTEN_PLATFORM_MACOSX)
 #include <Swift/QtUI/CocoaUIHelpers.h>
 #elif defined(SWIFTEN_PLATFORM_WINDOWS)
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 3e6e1d3..627cc17 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -6,16 +6,18 @@
 
 #pragma once
 
+#include <vector>
+
 #include <QWidget>
 #include <QMenu>
 #include <QList>
-#include "Swift/Controllers/UIInterfaces/MainWindow.h"
-#include "Swift/QtUI/QtRosterHeader.h"
-#include "Swift/QtUI/EventViewer/QtEventWindow.h"
-#include "Swift/QtUI/ChatList/QtChatListWindow.h"
-#include "Swift/QtUI/QtLoginWindow.h"
 
-#include <vector>
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+
+#include <Swift/QtUI/QtRosterHeader.h>
+#include <Swift/QtUI/EventViewer/QtEventWindow.h>
+#include <Swift/QtUI/ChatList/QtChatListWindow.h>
+#include <Swift/QtUI/QtLoginWindow.h>
 
 class QComboBox;
 class QLineEdit;
diff --git a/Swift/QtUI/QtResourceHelper.cpp b/Swift/QtUI/QtResourceHelper.cpp
new file mode 100644
index 0000000..f76c438
--- /dev/null
+++ b/Swift/QtUI/QtResourceHelper.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/QtResourceHelper.h>
+
+namespace Swift {
+
+QString statusShowTypeToIconPath(StatusShow::Type type) {
+	QString iconString;
+	switch (type) {
+		case StatusShow::Online: iconString = "online";break;
+		case StatusShow::Away: iconString = "away";break;
+		case StatusShow::XA: iconString = "away";break;
+		case StatusShow::FFC: iconString = "online";break;
+		case StatusShow::DND: iconString = "dnd";break;
+		case StatusShow::None: iconString = "offline";break;
+	}
+	return QString(":/icons/%1.png").arg(iconString);
+}
+
+}
+
diff --git a/Swift/QtUI/QtResourceHelper.h b/Swift/QtUI/QtResourceHelper.h
new file mode 100644
index 0000000..034a941
--- /dev/null
+++ b/Swift/QtUI/QtResourceHelper.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QString>
+
+#include <Swiften/Elements/StatusShow.h>
+
+namespace Swift {
+
+QString statusShowTypeToIconPath(StatusShow::Type type);
+
+}
diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp
index 99f1f34..f9d3dd0 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.cpp
+++ b/Swift/QtUI/Roster/QtTreeWidget.cpp
@@ -4,27 +4,34 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Roster/QtTreeWidget.h"
+#include <Swift/QtUI/Roster/QtTreeWidget.h>
 
 #include <boost/smart_ptr/make_shared.hpp>
 #include <boost/bind.hpp>
 
 #include <QUrl>
 #include <QMimeData>
+#include <QObject>
+#include <QLabel>
+#include <QTimer>
+#include <QToolTip>
 
 #include <Swiften/Base/Platform.h>
+
 #include <Swift/Controllers/Roster/ContactRosterItem.h>
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
-#include <QtSwiftUtil.h>
 #include <Swift/Controllers/Settings/SettingsProvider.h>
+ 
 #include <Swift/QtUI/QtUISettingConstants.h>
 
+#include <QtSwiftUtil.h>
+
 namespace Swift {
 
-QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) {
+QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent), tooltipShown_(false) {
 	eventStream_ = eventStream;
 	settings_ = settings;
 	model_ = new RosterModel(this);
@@ -66,12 +73,25 @@ void QtTreeWidget::handleSettingChanged(const std::string& setting) {
 	}
 }
 
+void QtTreeWidget::handleRefreshTooltip() {
+	if (tooltipShown_) {
+		QPoint position = QCursor::pos();
+		QModelIndex index = indexAt(mapFromGlobal(position));
+		QToolTip::showText(position, model_->data(index, Qt::ToolTipRole).toString());
+	}
+}
+
 void QtTreeWidget::setRosterModel(Roster* roster) {
 	roster_ = roster;
 	model_->setRoster(roster);
 	expandAll();
 }
 
+void QtTreeWidget::refreshTooltip() {
+	// Qt needs some time to emit the events we need to detect tooltip's visibility correctly; 20 ms should be enough
+	QTimer::singleShot(20, this, SLOT(handleRefreshTooltip()));
+}
+
 QtTreeWidgetItem* QtTreeWidget::getRoot() {
 	return treeRoot_;
 }
@@ -161,6 +181,23 @@ void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) {
 	QTreeView::dragMoveEvent(event);
 }
 
+bool QtTreeWidget::event(QEvent* event) {
+	QChildEvent* childEvent = NULL;
+	if ((childEvent = dynamic_cast<QChildEvent*>(event))) {
+		if (childEvent->polished()) {
+			if (dynamic_cast<QLabel*>(childEvent->child())) {
+				tooltipShown_ = true;
+			}
+		}
+		else if (childEvent->removed()) {
+			if (childEvent->child()->objectName() == "qtooltip_label") {
+				tooltipShown_ = false;
+			}
+		}
+	}
+	return QAbstractItemView::event(event);
+}
+
 void QtTreeWidget::handleExpanded(const QModelIndex& index) {
 	GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer()));
 	if (item) {
diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h
index 7c10a6a..8884a40 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.h
+++ b/Swift/QtUI/Roster/QtTreeWidget.h
@@ -11,8 +11,9 @@
 #include <QDragEnterEvent>
 #include <QDropEvent>
 #include <QDragMoveEvent>
-#include "Swift/QtUI/Roster/RosterModel.h"
-#include "Swift/QtUI/Roster/RosterDelegate.h"
+ 
+#include <Swift/QtUI/Roster/RosterModel.h>
+#include <Swift/QtUI/Roster/RosterDelegate.h>
 
 namespace Swift {
 class UIEventStream;
@@ -27,6 +28,7 @@ class QtTreeWidget : public QTreeView{
 		QtTreeWidgetItem* getRoot();
 		void setRosterModel(Roster* roster);
 		Roster* getRoster() {return roster_;}
+		void refreshTooltip();
 		boost::signal<void (RosterItem*)> onSomethingSelectedChanged;
 
 	private slots:
@@ -36,15 +38,19 @@ class QtTreeWidget : public QTreeView{
 		void handleCollapsed(const QModelIndex&);
 		void handleClicked(const QModelIndex&);
 		void handleSettingChanged(const std::string& setting);
+		void handleRefreshTooltip();
+
 	protected:
 		void dragEnterEvent(QDragEnterEvent* event);
 		void dropEvent(QDropEvent* event);
 		void dragMoveEvent(QDragMoveEvent* event);
+		bool event(QEvent* event);
 
 	protected:
 		QModelIndexList getSelectedIndexes() const;
 	private:
 		void drawBranches(QPainter*, const QRect&, const QModelIndex&) const;
+
 	protected slots:
 		virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous);
 	protected:
@@ -56,6 +62,7 @@ class QtTreeWidget : public QTreeView{
 		RosterDelegate* delegate_;
 		QtTreeWidgetItem* treeRoot_;
 		SettingsProvider* settings_;
+		bool tooltipShown_;
 };
 
 }
diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp
index 3791ffa..16c6d7e 100644
--- a/Swift/QtUI/Roster/RosterModel.cpp
+++ b/Swift/QtUI/Roster/RosterModel.cpp
@@ -4,7 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "RosterModel.h"
+#include <Swift/QtUI/Roster/RosterModel.h>
 
 #include <boost/bind.hpp>
 
@@ -13,22 +13,26 @@
 #include <QMimeData>
 #include <qdebug.h>
 
-#include "Swiften/Elements/StatusShow.h"
-#include "Swift/Controllers/Roster/ContactRosterItem.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
-#include <Swift/Controllers/StatusUtil.h>
+#include <Swiften/Elements/StatusShow.h>
 #include <Swiften/Base/Path.h>
 
-#include "QtSwiftUtil.h"
-#include "Swift/QtUI/Roster/QtTreeWidget.h"
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+
+#include <Swift/QtUI/Roster/QtTreeWidget.h>
+#include <Swift/QtUI/Roster/RosterTooltip.h>
+#include <Swift/QtUI/QtResourceHelper.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
 
 namespace Swift {
 
-RosterModel::RosterModel(QtTreeWidget* view) : view_(view) {
-	roster_ = NULL;
+RosterModel::RosterModel(QtTreeWidget* view) : roster_(NULL), view_(view) {
+	const int tooltipAvatarSize = 96; // maximal suggested size according to XEP-0153
+	cachedImageScaler_ = new QtScaledAvatarCache(tooltipAvatarSize);
 }
 
 RosterModel::~RosterModel() {
+	delete cachedImageScaler_;
 }
 
 void RosterModel::setRoster(Roster* roster) {
@@ -63,6 +67,7 @@ void RosterModel::handleDataChanged(RosterItem* item) {
 	QModelIndex modelIndex = index(item);
 	if (modelIndex.isValid()) {
 		emit dataChanged(modelIndex, modelIndex);
+		view_->refreshTooltip();
 	}
 }
 
@@ -141,16 +146,7 @@ QString RosterModel::getToolTip(RosterItem* item) const {
 	QString tip(P2QSTRING(item->getDisplayName()));
 	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
 	if (contact) {
-		if (contact->getDisplayJID().isValid()) {
-			tip += "\n" + P2QSTRING(contact->getDisplayJID().toBare().toString());
-		}
-		tip += "\n " + P2QSTRING(statusShowTypeToFriendlyName(contact->getStatusShow()));
-		if (!getStatusText(item).isEmpty()) {
-			tip += ": " + getStatusText(item);
-		}
-		if (!contact->getIdleText().empty()) {
-			tip += "\n " + tr("Idle since ") + P2QSTRING(contact->getIdleText());
-		}
+		return RosterTooltip::buildDetailedTooltip(contact, cachedImageScaler_);
 	}
 	return tip;
 }
@@ -176,16 +172,7 @@ QIcon RosterModel::getPresenceIcon(RosterItem* item) const {
 		return QIcon(":/icons/stop.png");
 	}
 
-	QString iconString;
-	switch (contact->getStatusShow()) {
-		case StatusShow::Online: iconString = "online";break;
-		case StatusShow::Away: iconString = "away";break;
-		case StatusShow::XA: iconString = "away";break;
-		case StatusShow::FFC: iconString = "online";break;
-		case StatusShow::DND: iconString = "dnd";break;
-		case StatusShow::None: iconString = "offline";break;
-	}
-	return QIcon(":/icons/" + iconString + ".png");
+	return QIcon(statusShowTypeToIconPath(contact->getStatusShow()));
 }
 
 
diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h
index cae80c4..5397054 100644
--- a/Swift/QtUI/Roster/RosterModel.h
+++ b/Swift/QtUI/Roster/RosterModel.h
@@ -6,11 +6,13 @@
 
 #pragma once
 
-#include "Swift/Controllers/Roster/Roster.h"
-
 #include <QAbstractItemModel>
 #include <QList>
 
+#include <Swift/Controllers/Roster/Roster.h>
+
+#include <Swift/QtUI/QtScaledAvatarCache.h>
+
 namespace Swift {
 	enum RosterRoles {
 		StatusTextRole = Qt::UserRole,
@@ -56,5 +58,6 @@ namespace Swift {
 			void reLayout();
 			Roster* roster_;
 			QtTreeWidget* view_;
+			QtScaledAvatarCache* cachedImageScaler_;
 	};
 }
diff --git a/Swift/QtUI/Roster/RosterTooltip.cpp b/Swift/QtUI/Roster/RosterTooltip.cpp
new file mode 100644
index 0000000..edf9c99
--- /dev/null
+++ b/Swift/QtUI/Roster/RosterTooltip.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/Roster/RosterTooltip.h>
+
+#include <QObject>
+#include <QString>
+#include <QApplication>
+
+#include <Swiften/Base/Path.h>
+
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/StatusUtil.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtScaledAvatarCache.h>
+#include <Swift/QtUI/QtUtilities.h>
+#include <Swift/QtUI/QtResourceHelper.h>
+
+using namespace QtUtilities;
+
+namespace Swift {
+
+QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler) {
+	QString tooltipTemplate;
+	if (QApplication::layoutDirection() == Qt::RightToLeft) {
+		tooltipTemplate = QString(
+			"<table style='white-space:pre'>"
+				"<tr>"
+					"<td>"
+						"<img src=\"%1\" />"
+					"</td>"
+					"<td>"
+						"<p style='font-size: 14px'>%3 %2</p>"
+						"<table><tr><td valign='middle'>%5</td><td valign='middle'>%4</td></tr></table>"
+						"%6"
+						"%7"
+						"%8"
+					"</td>"
+				"</tr>"
+			"</table>");
+	} else {
+		tooltipTemplate = QString(
+			"<table style='white-space:pre'>"
+				"<tr>"
+					"<td>"
+						"<img src=\"%1\" />"
+					"</td>"
+					"<td>"
+						"<p style='font-size: 14px'>%2 %3</p>"
+						"<table><tr><td valign='middle'>%4</td><td valign='middle'>%5</td></tr></table>"
+						"%6"
+						"%7"
+						"%8"
+					"</td>"
+				"</tr>"
+			"</table>");
+	}
+	// prepare tooltip
+	QString fullName = P2QSTRING(contact->getDisplayName());
+
+	QString vCardSummary;
+	VCard::ref vCard = contact->getVCard();
+	if (vCard) {
+		fullName = P2QSTRING(vCard->getFullName()).trimmed();
+		if (fullName.isEmpty()) {
+			fullName = (P2QSTRING(vCard->getGivenName()) + " " + P2QSTRING(vCard->getFamilyName())).trimmed();
+		}
+		if (fullName.isEmpty()) {
+			fullName = P2QSTRING(contact->getDisplayName());
+		}
+		vCardSummary = buildVCardSummary(vCard);
+	} else {
+		contact->onVCardRequested();
+	}
+
+	QString scaledAvatarPath = cachedImageScaler->getScaledAvatarPath(P2QSTRING(contact->getAvatarPath().empty() ? ":/icons/avatar.png" : pathToString(contact->getAvatarPath())));
+
+	QString bareJID = contact->getDisplayJID().toString().empty() ? "" : "( " + P2QSTRING(contact->getDisplayJID().toString()) + " )";
+
+	QString presenceIconTag = QString("<img src='%1' />").arg(statusShowTypeToIconPath(contact->getStatusShow()));
+
+	QString statusMessage = contact->getStatusText().empty() ? QObject::tr("(No message)") : P2QSTRING(contact->getStatusText());
+
+	QString idleString = P2QSTRING(contact->getIdleText());
+	if (!idleString.isEmpty()) {
+		idleString = QObject::tr("Idle since %1").arg(idleString);
+		idleString = htmlEscape(idleString) + "<br/>";
+	}
+
+	QString lastSeen = P2QSTRING(contact->getOfflineSinceText());
+	if (!lastSeen.isEmpty()) {
+		lastSeen = QObject::tr("Last seen %1").arg(lastSeen);
+		lastSeen = htmlEscape(lastSeen) + "<br/>";
+	}
+
+	return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), idleString, lastSeen, vCardSummary);
+}
+
+QString RosterTooltip::buildVCardSummary(VCard::ref vcard) {
+	QString summary;
+	summary = "<table>";
+
+	// star | name | content
+	QString currentBlock;
+	foreach (const VCard::Telephone& tel, vcard->getTelephones()) {
+		QString field = buildVCardField(tel.isPreferred, QObject::tr("Telephone"), htmlEscape(P2QSTRING(tel.number)));
+		if (tel.isPreferred) {
+			currentBlock = field;
+			break;
+		}
+		currentBlock += field;
+	}
+	summary += currentBlock;
+
+	currentBlock = "";
+	foreach (const VCard::EMailAddress& mail, vcard->getEMailAddresses()) {
+		QString field = buildVCardField(mail.isPreferred, QObject::tr("E-Mail"), htmlEscape(P2QSTRING(mail.address)));
+		if (mail.isPreferred) {
+			currentBlock = field;
+			break;
+		}
+		currentBlock += field;
+	}
+	summary += currentBlock;
+
+	currentBlock = "";
+	foreach (const VCard::Organization& org, vcard->getOrganizations()) {
+		QString field = buildVCardField(false, QObject::tr("Organization"), htmlEscape(P2QSTRING(org.name)));
+		currentBlock += field;
+	}
+	summary += currentBlock;
+
+	currentBlock = "";
+	foreach(const std::string& title, vcard->getTitles()) {
+		QString field = buildVCardField(false, QObject::tr("Title"), htmlEscape(P2QSTRING(title)));
+		currentBlock += field;
+	}
+	summary += currentBlock;
+
+	summary += "</table>";
+	return summary;
+}
+
+QString RosterTooltip::buildVCardField(bool preferred, const QString& name, const QString& content) {
+	QString rowTemplate;
+	if (QApplication::layoutDirection() == Qt::RightToLeft) {
+		rowTemplate = QString("<tr><td>%3</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%1</td></tr>");
+	} else {
+		rowTemplate = QString("<tr><td>%1</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%3</td></tr>");
+	}
+	return  rowTemplate.arg(preferred ? "<img src=':/icons/star-checked.png' />" : "", name, content);
+}
+
+}
diff --git a/Swift/QtUI/Roster/RosterTooltip.h b/Swift/QtUI/Roster/RosterTooltip.h
new file mode 100644
index 0000000..37f5da2
--- /dev/null
+++ b/Swift/QtUI/Roster/RosterTooltip.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QString>
+
+#include <Swiften/Elements/VCard.h>
+
+namespace Swift {
+
+class ContactRosterItem;
+class QtScaledAvatarCache;
+
+class RosterTooltip {
+	public:
+		static QString buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler);
+
+	private:
+		static QString buildVCardSummary(VCard::ref vcard);
+		static QString buildVCardField(bool preferred, const QString& name, const QString& content);
+};
+
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 5cfe81f..2303189 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -147,6 +147,7 @@ sources = [
     "Roster/DelegateCommons.cpp",
     "Roster/QtRosterWidget.cpp",
     "Roster/QtOccupantListWidget.cpp",
+    "Roster/RosterTooltip.cpp",
     "EventViewer/EventModel.cpp",
     "EventViewer/EventDelegate.cpp",
     "EventViewer/TwoLineDelegate.cpp",
@@ -163,9 +164,9 @@ sources = [
     "MUCSearch/MUCSearchRoomItem.cpp",
     "MUCSearch/MUCSearchEmptyItem.cpp",
     "MUCSearch/MUCSearchDelegate.cpp",
-	"UserSearch/ContactListDelegate.cpp",
-	"UserSearch/ContactListModel.cpp",
-	"UserSearch/QtContactListWidget.cpp",
+    "UserSearch/ContactListDelegate.cpp",
+    "UserSearch/ContactListModel.cpp",
+    "UserSearch/QtContactListWidget.cpp",
     "UserSearch/QtSuggestingJIDInput.cpp",
     "UserSearch/QtUserSearchFirstPage.cpp",
     "UserSearch/QtUserSearchFirstMultiJIDPage.cpp",
@@ -175,11 +176,11 @@ sources = [
     "UserSearch/QtUserSearchWindow.cpp",
     "UserSearch/UserSearchModel.cpp",
     "UserSearch/UserSearchDelegate.cpp",
-	"Whiteboard/FreehandLineItem.cpp",
-	"Whiteboard/GView.cpp",
-	"Whiteboard/TextDialog.cpp",
-	"Whiteboard/QtWhiteboardWindow.cpp",
-	"Whiteboard/ColorWidget.cpp",
+    "Whiteboard/FreehandLineItem.cpp",
+    "Whiteboard/GView.cpp",
+    "Whiteboard/TextDialog.cpp",
+    "Whiteboard/QtWhiteboardWindow.cpp",
+    "Whiteboard/ColorWidget.cpp",
     "QtSubscriptionRequestWindow.cpp",
     "QtRosterHeader.cpp",
     "QtWebView.cpp",
@@ -189,7 +190,8 @@ sources = [
     "QtMUCConfigurationWindow.cpp",
     "QtAffiliationEditor.cpp",
     "QtUISettingConstants.cpp",
-    "QtURLValidator.cpp"
+    "QtURLValidator.cpp",
+    "QtResourceHelper.cpp"
   ]
 
 # QtVCardWidget
diff --git a/Swift/QtUI/UserSearch/ContactListModel.cpp b/Swift/QtUI/UserSearch/ContactListModel.cpp
index 6523a4d..4c4a3ea 100644
--- a/Swift/QtUI/UserSearch/ContactListModel.cpp
+++ b/Swift/QtUI/UserSearch/ContactListModel.cpp
@@ -6,12 +6,14 @@
 
 #include <Swift/QtUI/UserSearch/ContactListModel.h>
 
-#include <Swift/QtUI/QtSwiftUtil.h>
+#include <QMimeData>
+
 #include <Swiften/Base/Path.h>
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Elements/StatusShow.h>
 
-#include <QMimeData>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtResourceHelper.h>
 
 namespace Swift {
 
@@ -158,16 +160,7 @@ QVariant ContactListModel::dataForContact(const Contact& contact, int role) cons
 }
 
 QIcon ContactListModel::getPresenceIconForContact(const Contact& contact) const {
-	QString iconString;
-	switch (contact.statusType) {
-		case StatusShow::Online: iconString = "online";break;
-		case StatusShow::Away: iconString = "away";break;
-		case StatusShow::XA: iconString = "away";break;
-		case StatusShow::FFC: iconString = "online";break;
-		case StatusShow::DND: iconString = "dnd";break;
-		case StatusShow::None: iconString = "offline";break;
-	}
-	return QIcon(":/icons/" + iconString + ".png");
+	return QIcon(statusShowTypeToIconPath(contact.statusType));
 }
 
 }
-- 
cgit v0.10.2-6-g49f6