From 14fd8e4363241e04b20da85dfc61e5f315e9b28d Mon Sep 17 00:00:00 2001
From: Richard Maudsley <richard.maudsley@isode.com>
Date: Fri, 27 Jun 2014 17:06:49 +0100
Subject: Show own tooltip when hovering over roster header.

Test-Information:

Made combinations of presence/vcard/avatar changes and verified that the information in the tooltip was synchronized. Connect two clients and verify that the tooltip presence text reflects the local client presence only.

Change-Id: I92af0f58f7045f3a15f2fae2f9cbc6e24a066923

diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index df137f4..f16f8ad 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -339,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(), client_->getVCardManager());
+		rosterController_ = new RosterController(boundJID_, 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));
@@ -411,6 +411,7 @@ void MainController::handleConnected() {
 
 	client_->getVCardManager()->requestOwnVCard();
 	
+	rosterController_->setJID(boundJID_);
 	rosterController_->setEnabled(true);
 	rosterController_->getWindow()->setStreamEncryptionStatus(client_->isStreamEncrypted());
 	profileController_->setAvailable(true);
diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp
index d2d024c..6bb5119 100644
--- a/Swift/Controllers/Roster/RosterController.cpp
+++ b/Swift/Controllers/Roster/RosterController.cpp
@@ -58,10 +58,9 @@ 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, 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) {
+	: myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), vcardManager_(vcardManager), avatarManager_(avatarManager), nickManager_(nickManager), nickResolver_(nickResolver), presenceOracle_(presenceOracle), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) {
 	assert(fileTransferOverview);
 	iqRouter_ = iqRouter;
-	presenceOracle_ = presenceOracle;
 	subscriptionManager_ = subscriptionManager;
 	eventController_ = eventController;
 	settings_ = settings;
@@ -78,9 +77,11 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata
 	subscriptionManager_->onPresenceSubscriptionRequest.connect(boost::bind(&RosterController::handleSubscriptionRequest, this, _1, _2));
 	presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handleIncomingPresence, this, _1));
 	uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1));
-	avatarManager_ = avatarManager;
+
+	vcardManager_->onOwnVCardChanged.connect(boost::bind(&RosterController::handleOwnVCardChanged, this, _1));
 	avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1));
-	mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_)));
+	presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handlePresenceChanged, this, _1));
+	mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare())));
 
 	nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1));
 	mainWindow_->setMyJID(jid);
@@ -91,6 +92,11 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata
 	settings_->onSettingChanged.connect(boost::bind(&RosterController::handleSettingChanged, this, _1));
 
 	handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE));
+
+	ownContact_ = boost::make_shared<ContactRosterItem>(myJID_.toBare(), myJID_.toBare(), nickManager_->getOwnNick(), (GroupRosterItem*)0);
+	ownContact_->setVCard(vcardManager_->getVCard(myJID_.toBare()));
+	ownContact_->setAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare())));
+	mainWindow_->setMyContactRosterItem(ownContact_);
 }
 
 
@@ -336,12 +342,26 @@ void RosterController::handleSubscriptionRequestDeclined(SubscriptionRequestEven
 	subscriptionManager_->cancelSubscription(event->getJID());
 }
 
+void RosterController::handleOwnVCardChanged(VCard::ref vcard) {
+	ownContact_->setVCard(vcard);
+	mainWindow_->setMyContactRosterItem(ownContact_);
+}
+
 void RosterController::handleAvatarChanged(const JID& jid) {
 	boost::filesystem::path path = avatarManager_->getAvatarPath(jid);
 	roster_->applyOnItems(SetAvatar(jid, path));
-	if (jid.equals(myJID_, JID::WithoutResource)) {
+	if (jid.equals(myJID_, JID::WithResource)) {
 		mainWindow_->setMyAvatarPath(pathToString(path));
 	}
+	ownContact_->setAvatarPath(pathToString(path));
+	mainWindow_->setMyContactRosterItem(ownContact_);
+}
+
+void RosterController::handlePresenceChanged(Presence::ref presence) {
+	if (presence->getFrom().equals(myJID_, JID::WithResource)) {
+		ownContact_->applyPresence(std::string(), presence);
+		mainWindow_->setMyContactRosterItem(ownContact_);
+	}
 }
 
 boost::optional<XMPPRosterItem> RosterController::getItem(const JID& jid) const {
diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h
index 5b26fc7..d0c7024 100644
--- a/Swift/Controllers/Roster/RosterController.h
+++ b/Swift/Controllers/Roster/RosterController.h
@@ -50,10 +50,13 @@ namespace Swift {
 			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();
+			void setJID(const JID& jid) { myJID_ = jid; }
 			MainWindow* getWindow() {return mainWindow_;}
 			boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest;
 			boost::signal<void ()> onSignOutRequest;
+			void handleOwnVCardChanged(VCard::ref vcard);
 			void handleAvatarChanged(const JID& jid);
+			void handlePresenceChanged(Presence::ref presence);
 			void setEnabled(bool enabled);
 
 			boost::optional<XMPPRosterItem> getItem(const JID&) const;
@@ -93,6 +96,7 @@ namespace Swift {
 			MainWindow* mainWindow_;
 			Roster* roster_;
 			OfflineRosterFilter* offlineFilter_;
+			VCardManager* vcardManager_;
 			AvatarManager* avatarManager_;
 			NickManager* nickManager_;
 			NickResolver* nickResolver_;
@@ -107,6 +111,7 @@ namespace Swift {
 			FileTransferOverview* ftOverview_;
 			ClientBlockListManager* clientBlockListManager_;
 			RosterVCardProvider* rosterVCardProvider_;
+			boost::shared_ptr<ContactRosterItem> ownContact_;
 			
 			boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
 			boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index c85106f..82750bf 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -15,6 +15,7 @@
 #include <Swiften/Elements/DiscoItems.h>
 #include <Swiften/TLS/Certificate.h>
 #include <Swiften/Base/boost_bsignals.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
 
 namespace Swift {
 	class Roster;
@@ -33,6 +34,7 @@ namespace Swift {
 			virtual void setMyAvatarPath(const std::string& path) = 0;
 			virtual void setMyStatusText(const std::string& status) = 0;
 			virtual void setMyStatusType(StatusShow::Type type) = 0;
+			virtual void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) = 0;
 			/** Must be able to cope with NULL to clear the roster */
 			virtual void setRosterModel(Roster* roster) = 0;
 			virtual void setConnecting() = 0;
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index ff3fa4d..b56f352 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -20,6 +20,7 @@ namespace Swift {
 			virtual void setMyAvatarPath(const std::string& /*path*/) {}
 			virtual void setMyStatusText(const std::string& /*status*/) {}
 			virtual void setMyStatusType(StatusShow::Type /*type*/) {}
+			virtual void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> /*contact*/) {}
 			virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {}
 			virtual void setConnecting() {}
 			virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {}
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 1acc519..31a8234 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -340,6 +340,10 @@ void QtMainWindow::setMyStatusType(StatusShow::Type type) {
 	meView_->setStatusType(type);
 }
 
+void QtMainWindow::setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) {
+	meView_->setContactRosterItem(contact);
+}
+
 void QtMainWindow::setConnecting() {
 	meView_->setConnecting();
 }
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index c489a9e..f1f6900 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -47,6 +47,7 @@ namespace Swift {
 			void setMyAvatarPath(const std::string& path);
 			void setMyStatusText(const std::string& status);
 			void setMyStatusType(StatusShow::Type type);
+			void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact);
 			void setConnecting();
 			void setStreamEncryptionStatus(bool tlsInPlaceAndValid);
 			void openCertificateDialog(const std::vector<Certificate::ref>& chain);
diff --git a/Swift/QtUI/QtRosterHeader.cpp b/Swift/QtUI/QtRosterHeader.cpp
index 2c8f244..69a0ef6 100644
--- a/Swift/QtUI/QtRosterHeader.cpp
+++ b/Swift/QtUI/QtRosterHeader.cpp
@@ -6,20 +6,23 @@
 
 #include "QtRosterHeader.h"
 
-#include <QHBoxLayout>
-#include <QVBoxLayout>
+#include <QBitmap>
+#include <qdebug.h>
 #include <QFileInfo>
+#include <QHBoxLayout>
+#include <QHelpEvent>
 #include <QIcon>
-#include <QSizePolicy>
-#include <qdebug.h>
 #include <QMouseEvent>
 #include <QPainter>
-#include <QBitmap>
+#include <QSizePolicy>
+#include <QToolTip>
+#include <QVBoxLayout>
 
 #include "QtStatusWidget.h"
 #include <Swift/QtUI/QtElidingLabel.h>
 #include <Swift/QtUI/QtClickableLabel.h>
 #include <Swift/QtUI/QtNameWidget.h>
+#include <Swift/QtUI/Roster/RosterTooltip.h>
 #include "QtScaledAvatarCache.h"
 
 namespace Swift {
@@ -89,6 +92,17 @@ void QtRosterHeader::setStreamEncryptionStatus(bool tlsInPlace) {
 	securityInfoButton_->setVisible(tlsInPlace);
 }
 
+bool QtRosterHeader::event(QEvent* event) {
+	if (event->type() == QEvent::ToolTip) {
+		QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
+		QtScaledAvatarCache scaledAvatarCache(avatarSize_);
+		QString text = RosterTooltip::buildDetailedTooltip(contact_.get(), &scaledAvatarCache);
+		QToolTip::showText(helpEvent->globalPos(), text);
+		return true;
+	}
+	return QWidget::event(event);
+}
+
 void QtRosterHeader::setAvatar(const QString& path) {
 	QString scaledAvatarPath = QtScaledAvatarCache(avatarSize_).getScaledAvatarPath(path);
 	QPixmap avatar;
@@ -105,6 +119,10 @@ void QtRosterHeader::setNick(const QString& nick) {
 	nameWidget_->setNick(nick);
 }
 
+void QtRosterHeader::setContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) {
+	contact_ = contact;
+}
+
 void QtRosterHeader::setJID(const QString& jid) {
 	nameWidget_->setJID(jid);
 }
diff --git a/Swift/QtUI/QtRosterHeader.h b/Swift/QtUI/QtRosterHeader.h
index ad19178..eafbc02 100644
--- a/Swift/QtUI/QtRosterHeader.h
+++ b/Swift/QtUI/QtRosterHeader.h
@@ -13,7 +13,9 @@
 #include <QToolButton>
 
 #include <string>
-#include "Swiften/Elements/StatusShow.h"
+#include <Swiften/Elements/StatusShow.h>
+#include <Swiften/Elements/VCard.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
 
 #include "QtTextEdit.h"
 
@@ -34,11 +36,14 @@ namespace Swift {
 
 		void setJID(const QString& jid);
 		void setNick(const QString& nick);
+		void setContactRosterItem(boost::shared_ptr<ContactRosterItem> contact);
 
 		void setStatusText(const QString& statusMessage);
 		void setStatusType(StatusShow::Type type);
 		void setConnecting();
 		void setStreamEncryptionStatus(bool tlsInPlace);
+	private:
+		bool event(QEvent* event);
 	signals:
 		void onChangeStatusRequest(StatusShow::Type showType, const QString &statusMessage);
 		void onEditProfileRequest();
@@ -54,5 +59,6 @@ namespace Swift {
 		QtStatusWidget* statusWidget_;
 		QToolButton* securityInfoButton_;
 		static const int avatarSize_;
+		boost::shared_ptr<ContactRosterItem> contact_;
 	};
 }
-- 
cgit v0.10.2-6-g49f6