diff options
| author | Remko Tronçon <git@el-tramo.be> | 2010-12-17 13:21:14 (GMT) | 
|---|---|---|
| committer | Remko Tronçon <git@el-tramo.be> | 2011-01-30 15:10:02 (GMT) | 
| commit | b897bac235a95f9c4654b31d101779bd0cc8f72f (patch) | |
| tree | 8d00b3ab58ec200adf670e01671eed91876b485d | |
| parent | 869c52b244c2d03313e9eda83fac05bf0fc3a619 (diff) | |
| download | swift-contrib-b897bac235a95f9c4654b31d101779bd0cc8f72f.zip swift-contrib-b897bac235a95f9c4654b31d101779bd0cc8f72f.tar.bz2 | |
Added profile edit dialog.
Resolves: #141, #587.
28 files changed, 679 insertions, 4 deletions
| diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index f07a964..d7e1941 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -61,6 +61,7 @@  #include "Swift/Controllers/CertificateStorageFactory.h"  #include "Swift/Controllers/CertificateStorageTrustChecker.h"  #include "Swiften/Network/NetworkFactories.h" +#include <Swift/Controllers/ProfileController.h>  namespace Swift { @@ -98,6 +99,7 @@ MainController::MainController(  	rosterController_ = NULL;  	chatsManager_ = NULL;  	eventWindowController_ = NULL; +	profileController_ = NULL;  	userSearchControllerChat_ = NULL;  	userSearchControllerAdd_ = NULL;  	quitRequested_ = false; @@ -170,6 +172,8 @@ MainController::~MainController() {  void MainController::resetClient() {  	resetCurrentError();  	resetPendingReconnects(); +	delete profileController_; +	profileController_ = NULL;  	delete eventWindowController_;  	eventWindowController_ = NULL;  	delete chatsManager_; @@ -228,6 +232,8 @@ void MainController::handleConnected() {  	bool freshLogin = rosterController_ == NULL;  	myStatusLooksOnline_ = true;  	if (freshLogin) { +		profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); +  		rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_);  		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));  		rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); @@ -261,6 +267,7 @@ void MainController::handleConnected() {  	client_->getVCardManager()->requestOwnVCard();  	rosterController_->setEnabled(true); +	profileController_->setAvailable(true);  	/* Send presence later to catch all the incoming presences. */  	sendPresence(statusTracker_->getNextPresence());  	/* Enable chats last of all, so rejoining MUCs has the right sent presence */ @@ -522,6 +529,9 @@ void MainController::setManagersOffline() {  	if (rosterController_) {  		rosterController_->setEnabled(false);  	} +	if (profileController_) { +		profileController_->setAvailable(false); +	}  }  void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error) { diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index a933a5a..d6e54ef 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -42,6 +42,7 @@ namespace Swift {  	class EventLoop;  	class MUCController;  	class Notifier; +	class ProfileController;  	class TogglableNotifier;  	class PresenceNotifier;  	class EventNotifier; @@ -130,6 +131,7 @@ namespace Swift {  			UIEventStream* uiEventStream_;  			XMLConsoleController* xmlConsoleController_;  			ChatsManager* chatsManager_; +			ProfileController* profileController_;  			JID jid_;  			JID boundJID_;  			SystemTrayController* systemTrayController_; diff --git a/Swift/Controllers/ProfileController.cpp b/Swift/Controllers/ProfileController.cpp new file mode 100644 index 0000000..9667d89 --- /dev/null +++ b/Swift/Controllers/ProfileController.cpp @@ -0,0 +1,90 @@ +/* + * 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/ProfileController.h> + +#include <boost/bind.hpp> + +#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> +#include <Swiften/VCards/VCardManager.h> + + +namespace Swift { + +ProfileController::ProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream) : vcardManager(vcardManager), profileWindowFactory(profileWindowFactory), uiEventStream(uiEventStream), available(true), profileWindow(NULL), gettingVCard(false) { +	uiEventStream->onUIEvent.connect(boost::bind(&ProfileController::handleUIEvent, this, _1)); +} + +ProfileController::~ProfileController() { +	if (profileWindow) { +		vcardManager->onOwnVCardChanged.disconnect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); +		profileWindow->onVCardChangeRequest.disconnect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); +		delete profileWindow; +	} +	uiEventStream->onUIEvent.disconnect(boost::bind(&ProfileController::handleUIEvent, this, _1)); +} + +void ProfileController::handleUIEvent(UIEvent::ref event) { +	if (!boost::dynamic_pointer_cast<RequestProfileEditorUIEvent>(event)) { +		return; +	} + +	if (!profileWindow) { +		profileWindow = profileWindowFactory->createProfileWindow(); +		profileWindow->onVCardChangeRequest.connect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); +		vcardManager->onOwnVCardChanged.connect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); +	} +	gettingVCard = true; +	updateDialogStatus(); +	vcardManager->requestOwnVCard(); +	profileWindow->show(); +} + +void ProfileController::handleVCardChangeRequest(VCard::ref vcard) { +	assert(!pendingSetVCardRequest); +	profileWindow->setError(""); +	pendingSetVCardRequest = vcardManager->createSetVCardRequest(vcard); +	pendingSetVCardRequest->onResponse.connect(boost::bind(&ProfileController::handleSetVCardResponse, this, _2)); +	pendingSetVCardRequest->send(); +	updateDialogStatus(); +} + +void ProfileController::handleSetVCardResponse(ErrorPayload::ref error) { +	pendingSetVCardRequest.reset(); +	updateDialogStatus(); +	if (error) { +		profileWindow->setError("There was an error publishing your profile data"); +	} +	else { +		profileWindow->setError(""); +		profileWindow->hide(); +	} +} + +void ProfileController::handleOwnVCardChanged(VCard::ref vcard) { +	if (profileWindow) { +		profileWindow->setVCard(vcard); +		gettingVCard = false; +		updateDialogStatus(); +	} +} + +void ProfileController::setAvailable(bool b) { +	available = b; +	updateDialogStatus(); +} + + +void ProfileController::updateDialogStatus() { +	if (profileWindow) { +		profileWindow->setEnabled(available && !gettingVCard && !pendingSetVCardRequest); +		profileWindow->setProcessing(gettingVCard || pendingSetVCardRequest); +	} +} + +} diff --git a/Swift/Controllers/ProfileController.h b/Swift/Controllers/ProfileController.h new file mode 100644 index 0000000..c1afcf9 --- /dev/null +++ b/Swift/Controllers/ProfileController.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/VCards/SetVCardRequest.h> + +namespace Swift { +	class UIEventStream; +	class ProfileWindowFactory; +	class ProfileWindow; +	class VCardManager; + +	class ProfileController { +		public: +			ProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream); +			~ProfileController(); + +			void setAvailable(bool b); + +		private: +			void handleUIEvent(UIEvent::ref event); +			void handleVCardChangeRequest(VCard::ref vcard); +			void handleSetVCardResponse(ErrorPayload::ref); +			void handleOwnVCardChanged(VCard::ref vcard); +			void updateDialogStatus(); + +		private: +			VCardManager* vcardManager; +			ProfileWindowFactory* profileWindowFactory; +			UIEventStream* uiEventStream; +			bool available; +			SetVCardRequest::ref pendingSetVCardRequest; +			ProfileWindow* profileWindow; +			bool gettingVCard; +	}; +} + diff --git a/Swift/Controllers/RosterController.cpp b/Swift/Controllers/RosterController.cpp index 282f041..0f149f6 100644 --- a/Swift/Controllers/RosterController.cpp +++ b/Swift/Controllers/RosterController.cpp @@ -7,6 +7,7 @@  #include "Swift/Controllers/RosterController.h"  #include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp>  #include "Swiften/Base/foreach.h"  #include "Swift/Controllers/UIInterfaces/MainWindow.h" @@ -32,6 +33,7 @@  #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h"  #include "Swift/Controllers/UIEvents/RegroupRosterItemUIEvent.h"  #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h"  #include <Swiften/Client/NickManager.h>  namespace Swift { @@ -42,7 +44,7 @@ static const String SHOW_OFFLINE = "showOffline";   * 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) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver) { + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream) {  	iqRouter_ = iqRouter;  	presenceOracle_ = presenceOracle;  	subscriptionManager_ = subscriptionManager; @@ -54,6 +56,7 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata  	changeStatusConnection_ = mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2));  	signOutConnection_ = mainWindow_->onSignOutRequest.connect(boost::bind(boost::ref(onSignOutRequest))); +	mainWindow_->onEditProfileRequest.connect(boost::bind(&RosterController::handleEditProfileRequest, this));  	xmppRoster_->onJIDAdded.connect(boost::bind(&RosterController::handleOnJIDAdded, this, _1));  	xmppRoster_->onJIDUpdated.connect(boost::bind(&RosterController::handleOnJIDUpdated, this, _1, _2, _3));  	xmppRoster_->onJIDRemoved.connect(boost::bind(&RosterController::handleOnJIDRemoved, this, _1)); @@ -79,6 +82,8 @@ RosterController::~RosterController() {  	delete offlineFilter_;  	delete expandiness_; + +	mainWindow_->onEditProfileRequest.disconnect(boost::bind(&RosterController::handleEditProfileRequest, this));  	mainWindow_->setRosterModel(NULL);  	if (mainWindow_->canDelete()) {  		delete mainWindow_; @@ -280,4 +285,8 @@ void RosterController::handleAvatarChanged(const JID& jid) {  	}  } +void RosterController::handleEditProfileRequest() { +	uiEventStream_->onUIEvent(boost::make_shared<RequestProfileEditorUIEvent>()); +} +  } diff --git a/Swift/Controllers/RosterController.h b/Swift/Controllers/RosterController.h index 79c14b9..9e22686 100644 --- a/Swift/Controllers/RosterController.h +++ b/Swift/Controllers/RosterController.h @@ -60,6 +60,8 @@ namespace Swift {  			void handleUIEvent(boost::shared_ptr<UIEvent> event);  			void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload);  			void applyAllPresenceTo(const JID& jid); +			void handleEditProfileRequest(); +  			JID myJID_;  			XMPPRoster* xmppRoster_;  			MainWindowFactory* mainWindowFactory_; @@ -75,6 +77,7 @@ namespace Swift {  			RosterGroupExpandinessPersister* expandiness_;  			IQRouter* iqRouter_;  			SettingsProvider* settings_; +			UIEventStream* uiEventStream_;  			boost::bsignals::scoped_connection changeStatusConnection_;  			boost::bsignals::scoped_connection signOutConnection_;  			boost::bsignals::scoped_connection uiEventConnection_; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 96674bd..b149cd2 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -29,6 +29,7 @@ if env["SCONS_STAGE"] == "build" :  			"Chat/UserSearchController.cpp",  			"DiscoServiceWalker.cpp",  			"MainController.cpp", +			"ProfileController.cpp",  			"RosterController.cpp",  			"RosterGroupExpandinessPersister.cpp",  			"EventWindowController.cpp", diff --git a/Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h b/Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h new file mode 100644 index 0000000..107e2e8 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIEvents/UIEvent.h" + +namespace Swift { +	class RequestProfileEditorUIEvent : public UIEvent { +		public: +			RequestProfileEditorUIEvent() {} +	}; +} diff --git a/Swift/Controllers/UIEvents/UIEvent.h b/Swift/Controllers/UIEvents/UIEvent.h index ab57634..e79a4f1 100644 --- a/Swift/Controllers/UIEvents/UIEvent.h +++ b/Swift/Controllers/UIEvents/UIEvent.h @@ -6,9 +6,13 @@  #pragma once +#include <boost/shared_ptr.hpp> +  namespace Swift {  	class UIEvent {  		public: +			typedef boost::shared_ptr<UIEvent> ref; +  			virtual ~UIEvent();  	};  } diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 125aae5..55087fe 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -36,6 +36,7 @@ namespace Swift {  			boost::signal<void (StatusShow::Type, const String&)> onChangeStatusRequest;  			boost::signal<void ()> onSignOutRequest; +			boost::signal<void ()> onEditProfileRequest;  		private:  			bool canDelete_; diff --git a/Swift/Controllers/UIInterfaces/ProfileWindow.h b/Swift/Controllers/UIInterfaces/ProfileWindow.h new file mode 100644 index 0000000..e9c9a63 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/ProfileWindow.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Elements/VCard.h> + +namespace Swift { +	class ProfileWindow { +		public: +			virtual ~ProfileWindow() {}; + +			virtual void setVCard(VCard::ref vcard) = 0; + +			virtual void setEnabled(bool b) = 0; +			virtual void setProcessing(bool b) = 0; +			virtual void setError(const String&) = 0; + +			virtual void show() = 0; +			virtual void hide() = 0; + +			boost::signal<void (VCard::ref)> onVCardChangeRequest; +	}; +} diff --git a/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h new file mode 100644 index 0000000..022c3eb --- /dev/null +++ b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/ProfileWindow.h> + +namespace Swift { +	class ProfileWindowFactory { +		public: +			virtual ~ProfileWindowFactory() {}; + +			virtual ProfileWindow* createProfileWindow() = 0; +	}; +} diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 4783dc8..11623d7 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -15,6 +15,7 @@  #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h>  namespace Swift {  	class UIFactory :  @@ -26,7 +27,8 @@ namespace Swift {  			public MUCSearchWindowFactory,   			public XMLConsoleWidgetFactory,   			public UserSearchWindowFactory,  -			public JoinMUCWindowFactory { +			public JoinMUCWindowFactory, +			public ProfileWindowFactory {  		public:  			virtual ~UIFactory() {}  	}; diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp new file mode 100644 index 0000000..1ee7c73 --- /dev/null +++ b/Swift/QtUI/QtAvatarWidget.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtAvatarWidget.h" + +#include <QLabel> +#include <QVBoxLayout> +#include <QPixmap> +#include <QMenu> +#include <QAction> +#include <QMouseEvent> +#include <QFileDialog> +#include <QImageReader> +#include <QBuffer> +#include <QMessageBox> +#include <QPainter> + +#include <QtSwiftUtil.h> + +namespace Swift { + +QtAvatarWidget::QtAvatarWidget(QWidget* parent) : QWidget(parent) { +	QVBoxLayout* layout = new QVBoxLayout(this); +	layout->setContentsMargins(0,0,0,0); + +	QSizePolicy sp(QSizePolicy::Fixed, QSizePolicy::Fixed); +	sp.setHorizontalStretch(0); +	sp.setVerticalStretch(0); +	setSizePolicy(sp); +	setMinimumSize(QSize(96, 96)); +	setMaximumSize(QSize(96, 96)); + +	label = new QLabel(this); +	label->setWordWrap(true); +	label->setSizePolicy(sp); +	label->setMinimumSize(QSize(96, 96)); +	label->setMaximumSize(QSize(96, 96)); +	label->setAlignment(Qt::AlignCenter); +	layout->addWidget(label); +} + +void QtAvatarWidget::setAvatar(const ByteArray& data, const String& type) { +	this->data = data; +	this->type = type; + +	QImage image; +	if (!data.isEmpty()) { +		image.loadFromData(reinterpret_cast<const uchar*>(data.getData()), data.getSize()); +	} + +	if (image.isNull()) { +		image = QImage(":/icons/no-avatar.png"); +		QPainter painter(&image); +		painter.setPen(Qt::gray); +		painter.drawText(0, 0, image.height(), image.width(), Qt::AlignHCenter | Qt::AlignVCenter, "No picture"); +	} + +	if (image.height() > label->height() || image.width() > label->width()) { +		image = image.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); +	} +	label->setPixmap(QPixmap::fromImage(image)); +} + +void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { +	QMenu menu; + +	QAction* selectPicture = new QAction("Select picture ...", this); +	menu.addAction(selectPicture); + +	QAction* clearPicture = new QAction("Clear picture", this); +	menu.addAction(clearPicture); + +	QAction* result = menu.exec(event->globalPos()); +	if (result == selectPicture) { +		QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.gif)")); +		if (!fileName.isEmpty()) { +			ByteArray data; +			data.readFromFile(Q2PSTRING(fileName)); + +			QBuffer buffer; +			buffer.setData(data.getData(), data.getSize()); +			buffer.open(QIODevice::ReadOnly); +			QString type = QImageReader::imageFormat(&buffer).toLower(); +			if (!type.isEmpty()) { +				type = "image/" + type; +				setAvatar(data, Q2PSTRING(type)); +			} +			else { +				QMessageBox::critical(this, "Error", "The selected picture is in an unrecognized format"); +			} +		} +	} +	else if (result == clearPicture) { +		setAvatar(ByteArray(), ""); +	} +} + + + +} diff --git a/Swift/QtUI/QtAvatarWidget.h b/Swift/QtUI/QtAvatarWidget.h new file mode 100644 index 0000000..ce4d192 --- /dev/null +++ b/Swift/QtUI/QtAvatarWidget.h @@ -0,0 +1,38 @@ +/* + * 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 <QWidget> +#include <QImage> +#include <Swiften/Base/ByteArray.h> + +class QLabel; + +namespace Swift { +	class QtAvatarWidget : public QWidget { +			Q_OBJECT +		public: +			QtAvatarWidget(QWidget* parent); + +			void setAvatar(const ByteArray& data, const String& type); + +			const ByteArray& getAvatarData() const { +				return data; +			} + +			const String& getAvatarType() const { +				return type; +			} + +			void mousePressEvent(QMouseEvent* event); + +		private: +			ByteArray data; +			String type; +			QLabel* label; +	}; +} diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 0411c0b..d313aba 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -83,6 +83,9 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS  	QMenu* actionsMenu = new QMenu(tr("&Actions"), this);  	menus_.push_back(actionsMenu); +	QAction* editProfileAction = new QAction("Edit Profile", this); +	connect(editProfileAction, SIGNAL(triggered()), SLOT(handleEditProfileAction())); +	actionsMenu->addAction(editProfileAction);  	QAction* joinMUCAction = new QAction("&Join Room", this);  	connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction()));  	actionsMenu->addAction(joinMUCAction); @@ -143,6 +146,10 @@ void QtMainWindow::handleSignOutAction() {  	onSignOutRequest();  } +void QtMainWindow::handleEditProfileAction() { +	onEditProfileRequest(); +} +  void QtMainWindow::handleJoinMUCAction() {  	uiEventStream_->send(boost::make_shared<RequestJoinMUCUIEvent>());  } @@ -190,5 +197,6 @@ void QtMainWindow::setConnecting() {  	meView_->setConnecting();  } +  } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 27972cb..938feff 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -54,6 +54,7 @@ namespace Swift {  			void handleShowOfflineToggled(bool);  			void handleJoinMUCAction();  			void handleSignOutAction(); +			void handleEditProfileAction();  			void handleAddUserActionTriggered(bool checked);  			void handleChatUserActionTriggered(bool checked);  			void handleEventCountUpdated(int count); diff --git a/Swift/QtUI/QtProfileWindow.cpp b/Swift/QtUI/QtProfileWindow.cpp new file mode 100644 index 0000000..0a53f11 --- /dev/null +++ b/Swift/QtUI/QtProfileWindow.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtProfileWindow.h" + +#include <QImage> +#include <QPixmap> +#include <QSizePolicy> +#include <QGridLayout> +#include <QLabel> +#include <QLineEdit> +#include <QPushButton> +#include <QMovie> + +#include "QtSwiftUtil.h" +#include "QtAvatarWidget.h" + +namespace Swift { + +QtProfileWindow::QtProfileWindow() { +	setWindowTitle("Edit Profile"); + +	QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); +	sizePolicy.setHorizontalStretch(0); +	sizePolicy.setVerticalStretch(0); +	sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); +	setSizePolicy(sizePolicy); + +	QVBoxLayout* layout = new QVBoxLayout(this); +	layout->setContentsMargins(10, 10, 10, 10); + +	QHBoxLayout* topLayout = new QHBoxLayout(); + +	avatar = new QtAvatarWidget(this); +	topLayout->addWidget(avatar); + +	QVBoxLayout* fieldsLayout = new QVBoxLayout(); + +	QHBoxLayout* horizontalLayout_2 = new QHBoxLayout(); +	nicknameLabel = new QLabel("Nickname: ", this); +	horizontalLayout_2->addWidget(nicknameLabel); +	nickname = new QLineEdit(this); +	horizontalLayout_2->addWidget(nickname); + +	fieldsLayout->addLayout(horizontalLayout_2); + +	errorLabel = new QLabel(this); +	errorLabel->setAlignment(Qt::AlignHCenter); +	fieldsLayout->addWidget(errorLabel); + +	fieldsLayout->addItem(new QSpacerItem(198, 17, QSizePolicy::Minimum, QSizePolicy::Expanding)); +	topLayout->addLayout(fieldsLayout); + +	layout->addLayout(topLayout); + +	QHBoxLayout* horizontalLayout = new QHBoxLayout(); +	horizontalLayout->setContentsMargins(0, 0, 0, 0); +	horizontalLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); + +	throbberLabel = new QLabel(this); +	throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); +	horizontalLayout->addWidget(throbberLabel); + +	saveButton = new QPushButton("Save", this); +	connect(saveButton, SIGNAL(clicked()), SLOT(handleSave())); +	horizontalLayout->addWidget(saveButton); + +	fieldsLayout->addLayout(horizontalLayout); + +	resize(360, 120); +} + +void QtProfileWindow::setVCard(Swift::VCard::ref vcard) { +	this->vcard = vcard; +	nickname->setText(P2QSTRING(vcard->getNickname())); +	avatar->setAvatar(vcard->getPhoto(), vcard->getPhotoType()); +} + +void QtProfileWindow::setEnabled(bool b) { +	nickname->setEnabled(b); +	nicknameLabel->setEnabled(b); +	avatar->setEnabled(b); +	saveButton->setEnabled(b); +} + +void QtProfileWindow::setProcessing(bool processing) { +	if (processing) { +		throbberLabel->movie()->start(); +		throbberLabel->show(); +	} +	else { +		throbberLabel->hide(); +		throbberLabel->movie()->stop(); +	} +} + +void QtProfileWindow::show() { +	QWidget::show(); +	QWidget::activateWindow(); +} + +void QtProfileWindow::hideEvent(QHideEvent* event) { +	QWidget::hideEvent(event); +} + +void QtProfileWindow::hide() { +	QWidget::hide(); +} + +void QtProfileWindow::handleSave() { +	assert(vcard); +	vcard->setNickname(Q2PSTRING(nickname->text())); +	vcard->setPhoto(avatar->getAvatarData()); +	vcard->setPhotoType(avatar->getAvatarType()); +	onVCardChangeRequest(vcard); +} + +void QtProfileWindow::setError(const String& error) { +	if (!error.isEmpty()) { +		errorLabel->setText("<font color='red'>" + P2QSTRING(error) + "</font>"); +	} +	else { +		errorLabel->setText(""); +	} +} + + + +} diff --git a/Swift/QtUI/QtProfileWindow.h b/Swift/QtUI/QtProfileWindow.h new file mode 100644 index 0000000..1ad73e8 --- /dev/null +++ b/Swift/QtUI/QtProfileWindow.h @@ -0,0 +1,48 @@ +/* + * 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 <QWidget> + +#include <Swift/Controllers/UIInterfaces/ProfileWindow.h> + +class QLabel; +class QLineEdit; +class QHBoxLayout; +class QPushButton; + +namespace Swift { +	class QtAvatarWidget; + +	class QtProfileWindow : public QWidget, public ProfileWindow { +			Q_OBJECT +		public: +			QtProfileWindow(); + +			void setVCard(Swift::VCard::ref); +			void setEnabled(bool); +			void setProcessing(bool); +			virtual void setError(const String&); +			void show(); +			void hide(); + +			void hideEvent (QHideEvent* event); + +		private slots: +			void handleSave(); + +		private: +			VCard::ref vcard; +	    QtAvatarWidget* avatar; +	    QLabel* nicknameLabel; +	    QLineEdit* nickname; +	    QLabel* throbberLabel; +	    QLabel* errorLabel; +	    QHBoxLayout* horizontalLayout; +	    QPushButton* saveButton; +	}; +} diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 8cb9863..953d658 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -21,6 +21,7 @@  #include "QtSwiftUtil.h"  #include "MUCSearch/QtMUCSearchWindow.h"  #include "UserSearch/QtUserSearchWindow.h" +#include "QtProfileWindow.h"  namespace Swift { @@ -87,4 +88,8 @@ JoinMUCWindow* QtUIFactory::createJoinMUCWindow() {  	return new QtJoinMUCWindow();  } +ProfileWindow* QtUIFactory::createProfileWindow() { +	return new QtProfileWindow(); +} +  } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 4d80338..199cebf 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -35,6 +35,7 @@ namespace Swift {  			virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);  			virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream);  			virtual JoinMUCWindow* createJoinMUCWindow(); +			virtual ProfileWindow* createProfileWindow();  		private slots:  			void handleLoginWindowGeometryChanged(); diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index b0072a6..05555f6 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -63,11 +63,13 @@ sources = [      "main.cpp",      "QtAboutWidget.cpp",      "QtAddContactDialog.cpp", +    "QtAvatarWidget.cpp",      "QtUIFactory.cpp",      "QtChatWindowFactory.cpp",      "QtChatWindow.cpp",      "QtLoginWindow.cpp",      "QtMainWindow.cpp", +    "QtProfileWindow.cpp",      "QtNameWidget.cpp",      "QtSettingsProvider.cpp",      "QtStatusWidget.cpp", diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index 9a80339..2f8d494 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -13,6 +13,7 @@  		<file alias="icons/error.png">../resources/icons/error.png</file>  		<file alias="icons/throbber.gif">../resources/icons/throbber.gif</file>  		<file alias="icons/avatar.png">../resources/icons/avatar.png</file> +		<file alias="icons/no-avatar.png">../resources/icons/no-avatar.png</file>  		<file alias="icons/tray-standard.png">../resources/icons/tray-standard.png</file>  		<file alias="icons/new-chat.png">../resources/icons/new-chat.png</file>  		<file alias="COPYING">../../COPYING</file> diff --git a/Swift/resources/icons/no-avatar.png b/Swift/resources/icons/no-avatar.pngBinary files differ new file mode 100644 index 0000000..c6dc381 --- /dev/null +++ b/Swift/resources/icons/no-avatar.png diff --git a/Swiften/VCards/SetVCardRequest.h b/Swiften/VCards/SetVCardRequest.h new file mode 100644 index 0000000..8dfda5d --- /dev/null +++ b/Swiften/VCards/SetVCardRequest.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Elements/VCard.h> + + +namespace Swift { +	class SetVCardRequest : public GenericRequest<VCard> { +		public: +			typedef boost::shared_ptr<SetVCardRequest> ref; + +			static ref create(VCard::ref vcard, IQRouter* router) { +				return ref(new SetVCardRequest(vcard, router)); +			} + +		private: +			SetVCardRequest(VCard::ref vcard, IQRouter* router) : GenericRequest<VCard>(IQ::Set, JID(), vcard, router) { +			} +	}; +} diff --git a/Swiften/VCards/UnitTest/VCardManagerTest.cpp b/Swiften/VCards/UnitTest/VCardManagerTest.cpp index 56bbfa1..1f81f8e 100644 --- a/Swiften/VCards/UnitTest/VCardManagerTest.cpp +++ b/Swiften/VCards/UnitTest/VCardManagerTest.cpp @@ -10,6 +10,7 @@  #include <cppunit/extensions/TestFactoryRegistry.h>  #include <vector>  #include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp>  #include "Swiften/VCards/VCardManager.h"  #include "Swiften/VCards/VCardMemoryStorage.h" @@ -28,6 +29,8 @@ class VCardManagerTest : public CppUnit::TestFixture {  		CPPUNIT_TEST(testRequest_VCardAlreadyRequested);  		CPPUNIT_TEST(testRequest_AfterPreviousRequest);  		CPPUNIT_TEST(testRequestOwnVCard); +		CPPUNIT_TEST(testCreateSetVCardRequest); +		CPPUNIT_TEST(testCreateSetVCardRequest_Error);  		CPPUNIT_TEST_SUITE_END();  	public: @@ -82,6 +85,8 @@ class VCardManagerTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), changes[0].first);  			CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), changes[0].second->getFullName());  			CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), vcardStorage->getVCard(JID("foo@bar.com/baz"))->getFullName()); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(ownChanges.size()));  		}  		void testRequest_Error() { @@ -125,12 +130,42 @@ class VCardManagerTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(ownJID.toBare(), changes[0].first);  			CPPUNIT_ASSERT_EQUAL(String("Myself"), changes[0].second->getFullName());  			CPPUNIT_ASSERT_EQUAL(String("Myself"), vcardStorage->getVCard(ownJID.toBare())->getFullName()); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(ownChanges.size())); +			CPPUNIT_ASSERT_EQUAL(String("Myself"), ownChanges[0]->getFullName()); +		} + +		void testCreateSetVCardRequest() { +			std::auto_ptr<VCardManager> testling = createManager(); +			VCard::ref vcard = boost::make_shared<VCard>(); +			vcard->setFullName("New Name"); +			SetVCardRequest::ref request = testling->createSetVCardRequest(vcard); +			request->send(); + +			stanzaChannel->onIQReceived(createSetVCardResult()); + +			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size())); +			CPPUNIT_ASSERT_EQUAL(ownJID.toBare(), changes[0].first); +			CPPUNIT_ASSERT_EQUAL(String("New Name"), changes[0].second->getFullName()); +		} + +		void testCreateSetVCardRequest_Error() { +			std::auto_ptr<VCardManager> testling = createManager(); +			VCard::ref vcard = boost::make_shared<VCard>(); +			vcard->setFullName("New Name"); +			SetVCardRequest::ref request = testling->createSetVCardRequest(vcard); +			request->send(); + +			stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + +			CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));  		}  	private:  		std::auto_ptr<VCardManager> createManager() {  			std::auto_ptr<VCardManager> manager(new VCardManager(ownJID, iqRouter, vcardStorage));  			manager->onVCardChanged.connect(boost::bind(&VCardManagerTest::handleVCardChanged, this, _1, _2)); +			manager->onOwnVCardChanged.connect(boost::bind(&VCardManagerTest::handleOwnVCardChanged, this, _1));  			return manager;  		} @@ -138,6 +173,10 @@ class VCardManagerTest : public CppUnit::TestFixture {  			changes.push_back(std::pair<JID, VCard::ref>(jid, vcard));  		} +		void handleOwnVCardChanged(VCard::ref vcard) { +			ownChanges.push_back(vcard); +		} +  		IQ::ref createVCardResult() {  			VCard::ref vcard(new VCard());  			vcard->setFullName("Foo Bar"); @@ -150,12 +189,18 @@ class VCardManagerTest : public CppUnit::TestFixture {  			return IQ::createResult(JID(), stanzaChannel->sentStanzas[0]->getID(), vcard);  		} +		IQ::ref createSetVCardResult() { +			return IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[0]->getID(), VCard::ref()); +		} + +  	private:  		JID ownJID;  		DummyStanzaChannel* stanzaChannel;  		IQRouter* iqRouter;  		VCardMemoryStorage* vcardStorage;  		std::vector< std::pair<JID, VCard::ref> > changes; +		std::vector<VCard::ref> ownChanges;  };  CPPUNIT_TEST_SUITE_REGISTRATION(VCardManagerTest); diff --git a/Swiften/VCards/VCardManager.cpp b/Swiften/VCards/VCardManager.cpp index 8d695d4..de53238 100644 --- a/Swiften/VCards/VCardManager.cpp +++ b/Swiften/VCards/VCardManager.cpp @@ -17,6 +17,9 @@ namespace Swift {  VCardManager::VCardManager(const JID& ownJID, IQRouter* iqRouter, VCardStorage* vcardStorage) : ownJID(ownJID), iqRouter(iqRouter), storage(vcardStorage) {  } +VCardManager::~VCardManager() { +} +  VCard::ref VCardManager::getVCard(const JID& jid) const {  	return storage->getVCard(jid);  } @@ -51,8 +54,27 @@ void VCardManager::handleVCardReceived(const JID& actualJID, VCard::ref vcard, E  	}  	requestedVCards.erase(actualJID);  	JID jid = actualJID.isValid() ? actualJID : ownJID.toBare(); +	setVCard(jid, vcard); +} + +SetVCardRequest::ref VCardManager::createSetVCardRequest(VCard::ref vcard) { +	SetVCardRequest::ref request = SetVCardRequest::create(vcard, iqRouter); +	request->onResponse.connect(boost::bind(&VCardManager::handleSetVCardResponse, this, vcard, _2)); +	return request; +} + +void VCardManager::handleSetVCardResponse(VCard::ref vcard, ErrorPayload::ref error) { +	if (!error) { +		setVCard(ownJID.toBare(), vcard); +	} +} + +void VCardManager::setVCard(const JID& jid, VCard::ref vcard) {  	storage->setVCard(jid, vcard);  	onVCardChanged(jid, vcard); +	if (jid.compare(ownJID, JID::WithoutResource) == 0) { +		onOwnVCardChanged(vcard); +	}  }  } diff --git a/Swiften/VCards/VCardManager.h b/Swiften/VCards/VCardManager.h index e1ed44a..5cdf82e 100644 --- a/Swiften/VCards/VCardManager.h +++ b/Swiften/VCards/VCardManager.h @@ -6,35 +6,48 @@  #pragma once -#include <boost/signals.hpp>  #include <set>  #include "Swiften/JID/JID.h"  #include "Swiften/Elements/VCard.h"  #include "Swiften/Elements/ErrorPayload.h" +#include <Swiften/VCards/SetVCardRequest.h> +#include <Swiften/Base/boost_bsignals.h>  namespace Swift {  	class JID;  	class VCardStorage;  	class IQRouter; -	class VCardManager { +	class VCardManager : public boost::bsignals::trackable {  		public:  			VCardManager(const JID& ownJID, IQRouter* iqRouter, VCardStorage* vcardStorage); +			~VCardManager();  			VCard::ref getVCard(const JID& jid) const;  			VCard::ref getVCardAndRequestWhenNeeded(const JID& jid);  			void requestVCard(const JID& jid);  			void requestOwnVCard(); +			SetVCardRequest::ref createSetVCardRequest(VCard::ref); +  		public:  			/**  			 * The JID will always be bare.  			 */  			boost::signal<void (const JID&, VCard::ref)> onVCardChanged; +			/** +			 * Emitted when our own vcard changes. +			 * +			 * onVCardChanged will also be emitted. +			 */ +			boost::signal<void (VCard::ref)> onOwnVCardChanged; +  		private:  			void handleVCardReceived(const JID& from, VCard::ref, ErrorPayload::ref); +			void handleSetVCardResponse(VCard::ref, ErrorPayload::ref); +			void setVCard(const JID& jid, VCard::ref vcard);  		private:  			JID ownJID; | 
 Swift
 Swift