summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2010-12-17 13:21:14 (GMT)
committerRemko Tronçon <git@el-tramo.be>2011-01-30 15:10:02 (GMT)
commitb897bac235a95f9c4654b31d101779bd0cc8f72f (patch)
tree8d00b3ab58ec200adf670e01671eed91876b485d
parent869c52b244c2d03313e9eda83fac05bf0fc3a619 (diff)
downloadswift-contrib-b897bac235a95f9c4654b31d101779bd0cc8f72f.zip
swift-contrib-b897bac235a95f9c4654b31d101779bd0cc8f72f.tar.bz2
Added profile edit dialog.
Resolves: #141, #587.
-rw-r--r--Swift/Controllers/MainController.cpp10
-rw-r--r--Swift/Controllers/MainController.h2
-rw-r--r--Swift/Controllers/ProfileController.cpp90
-rw-r--r--Swift/Controllers/ProfileController.h44
-rw-r--r--Swift/Controllers/RosterController.cpp11
-rw-r--r--Swift/Controllers/RosterController.h3
-rw-r--r--Swift/Controllers/SConscript1
-rw-r--r--Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h16
-rw-r--r--Swift/Controllers/UIEvents/UIEvent.h4
-rw-r--r--Swift/Controllers/UIInterfaces/MainWindow.h1
-rw-r--r--Swift/Controllers/UIInterfaces/ProfileWindow.h30
-rw-r--r--Swift/Controllers/UIInterfaces/ProfileWindowFactory.h18
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h4
-rw-r--r--Swift/QtUI/QtAvatarWidget.cpp103
-rw-r--r--Swift/QtUI/QtAvatarWidget.h38
-rw-r--r--Swift/QtUI/QtMainWindow.cpp8
-rw-r--r--Swift/QtUI/QtMainWindow.h1
-rw-r--r--Swift/QtUI/QtProfileWindow.cpp132
-rw-r--r--Swift/QtUI/QtProfileWindow.h48
-rw-r--r--Swift/QtUI/QtUIFactory.cpp5
-rw-r--r--Swift/QtUI/QtUIFactory.h1
-rw-r--r--Swift/QtUI/SConscript2
-rw-r--r--Swift/QtUI/Swift.qrc1
-rw-r--r--Swift/resources/icons/no-avatar.pngbin0 -> 3458 bytes
-rw-r--r--Swiften/VCards/SetVCardRequest.h26
-rw-r--r--Swiften/VCards/UnitTest/VCardManagerTest.cpp45
-rw-r--r--Swiften/VCards/VCardManager.cpp22
-rw-r--r--Swiften/VCards/VCardManager.h17
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.png
new file mode 100644
index 0000000..c6dc381
--- /dev/null
+++ b/Swift/resources/icons/no-avatar.png
Binary files differ
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;