From 41854ebf8c3376d6ee430efe3a9fdd25610bb2f5 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Tue, 27 Sep 2011 17:13:56 +0100 Subject: Allow room configuration. Resolves: #989 diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 79d376c..67bd74f 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -70,6 +70,7 @@ namespace Swift { virtual boost::optional getMessageTimestamp(boost::shared_ptr) const = 0; virtual void dayTicked() {}; virtual void handleBareJIDCapsChanged(const JID& jid) = 0; + std::string getErrorMessage(boost::shared_ptr); private: IDGenerator idGenerator_; @@ -78,7 +79,6 @@ namespace Swift { void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); void handleAllMessagesRead(); void handleSecurityLabelsCatalogResponse(boost::shared_ptr, ErrorPayload::ref error); - std::string getErrorMessage(boost::shared_ptr); void handleDayChangeTick(); protected: diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 178f4b6..56cc639 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -72,13 +72,15 @@ MUCController::MUCController ( chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); - chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this)); + chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); + muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); @@ -577,8 +579,23 @@ void MUCController::handleChangeSubjectRequest(const std::string& subject) { muc_->changeSubject(subject); } -void MUCController::handleConfigureRequest() { - muc_->requestConfigurationForm(); +void MUCController::handleConfigureRequest(Form::ref form) { + if (form) { + muc_->configureRoom(form); + } + else { + muc_->requestConfigurationForm(); + } +} + +void MUCController::handleConfigurationFailed(ErrorPayload::ref error) { + std::string errorMessage = getErrorMessage(error); + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); + chatWindow_->addErrorMessage(errorMessage); +} + +void MUCController::handleConfigurationFormReceived(Form::ref form) { + chatWindow_->showRoomConfigurationForm(form); } } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 08a3fc3..4be1488 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -88,7 +88,9 @@ namespace Swift { void dayTicked() {lastWasPresence_ = false;} void processUserPart(); void handleBareJIDCapsChanged(const JID& jid); - void handleConfigureRequest(); + void handleConfigureRequest(Form::ref); + void handleConfigurationFailed(ErrorPayload::ref); + void handleConfigurationFormReceived(Form::ref); private: MUC::ref muc_; diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index 7004324..75c92d3 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -14,8 +14,10 @@ #include #include -#include "Swiften/Elements/SecurityLabelsCatalog.h" -#include "Swiften/Elements/ChatState.h" +#include +#include +#include + namespace Swift { class AvatarManager; @@ -87,6 +89,8 @@ namespace Swift { * Actions that can be performed on the selected occupant. */ virtual void setAvailableOccupantActions(const std::vector& actions) = 0; + + virtual void showRoomConfigurationForm(Form::ref) = 0; boost::signal onClosed; boost::signal onAllMessagesRead; boost::signal onSendMessageRequest; @@ -97,7 +101,7 @@ namespace Swift { boost::signal onOccupantSelectionChanged; boost::signal onOccupantActionSelected; boost::signal onChangeSubjectRequest; - boost::signal onConfigureRequest; + boost::signal onConfigureRequest; // File transfer related boost::signal onFileTransferCancel; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 143e281..ad8856b 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -47,6 +47,7 @@ namespace Swift { virtual void setCorrectionEnabled(Tristate /*enabled*/) {} void setAvailableOccupantActions(const std::vector&/* actions*/) {} void setSubject(const std::string& /*subject*/) {} + virtual void showRoomConfigurationForm(Form::ref) {} boost::signal onClosed; boost::signal onAllMessagesRead; diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 2e3a225..1576880 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -162,6 +162,9 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt QtChatWindow::~QtChatWindow() { delete fileTransferJS; + if (mucConfigurationWindow) { + delete mucConfigurationWindow.data(); + } } @@ -364,7 +367,9 @@ void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { void QtChatWindow::setInputEnabled(bool enabled) { inputEnabled_ = enabled; -// input_->setEnabled(enabled); + if (!enabled && mucConfigurationWindow) { + delete mucConfigurationWindow.data(); + } } void QtChatWindow::showEvent(QShowEvent* event) { @@ -694,7 +699,7 @@ void QtChatWindow::setSubject(const std::string& subject) { void QtChatWindow::handleActionButtonClicked() { QMenu contextMenu; QAction* changeSubject = contextMenu.addAction(tr("Change subject")); - //QAction* configure = contextMenu.addAction(tr("Configure room")); + QAction* configure = contextMenu.addAction(tr("Configure room")); QAction* result = contextMenu.exec(QCursor::pos()); if (result == changeSubject) { bool ok; @@ -703,9 +708,17 @@ void QtChatWindow::handleActionButtonClicked() { onChangeSubjectRequest(Q2PSTRING(subject)); } } - //else if (result == configure) { - // onConfigureRequest(); - //} + else if (result == configure) { + onConfigureRequest(Form::ref()); + } +} + +void QtChatWindow::showRoomConfigurationForm(Form::ref form) { + if (mucConfigurationWindow) { + delete mucConfigurationWindow.data(); + } + mucConfigurationWindow = new QtMUCConfigurationWindow(form); + mucConfigurationWindow->onFormComplete.connect(boost::bind(boost::ref(onConfigureRequest), _1)); } } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index b8fa478..b011427 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -6,15 +6,17 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include +#include -#include "QtTabbable.h" +#include -#include "SwifTools/LastLineTracker.h" +#include -#include "Swiften/Base/IDGenerator.h" +#include #include +#include class QTextEdit; class QLineEdit; @@ -69,6 +71,7 @@ namespace Swift { QByteArray getSplitterState(); virtual void setAvailableOccupantActions(const std::vector& actions); void setSubject(const std::string& subject); + void showRoomConfigurationForm(Form::ref); public slots: void handleChangeSplitterState(QByteArray state); @@ -147,8 +150,8 @@ namespace Swift { QSplitter *logRosterSplitter_; Tristate correctionEnabled_; QString alertStyleSheet_; - std::map descriptions; QtFileTransferJSBridge* fileTransferJS; + QPointer mucConfigurationWindow; }; } diff --git a/Swift/QtUI/QtMUCConfigurationWindow.cpp b/Swift/QtUI/QtMUCConfigurationWindow.cpp new file mode 100644 index 0000000..e6be9b7 --- /dev/null +++ b/Swift/QtUI/QtMUCConfigurationWindow.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include + +#include +#include +#include + +namespace Swift { +QtMUCConfigurationWindow::QtMUCConfigurationWindow(Form::ref form) { + + setAttribute(Qt::WA_DeleteOnClose); + + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(2); + QLabel* label = new QLabel(this); + label->setText(tr("Room configuration")); + layout->addWidget(label); + + formWidget_ = NULL; + formWidget_ = new QtFormWidget(form, this); + layout->addWidget(formWidget_); + + QWidget* buttonsWidget = new QWidget(this); + layout->addWidget(buttonsWidget); + + QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget); + cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget); + buttonsLayout->addWidget(cancelButton_); + connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); + okButton_ = new QPushButton(tr("OK"), buttonsWidget); + buttonsLayout->addWidget(okButton_); + connect(okButton_, SIGNAL(clicked()), this, SLOT(handleOKClicked())); + show(); +} + +QtMUCConfigurationWindow::~QtMUCConfigurationWindow() { + +} + +void QtMUCConfigurationWindow::handleCancelClicked() { + close(); +} + +void QtMUCConfigurationWindow::handleOKClicked() { + onFormComplete(formWidget_->getCompletedForm()); + close(); +} + + +} diff --git a/Swift/QtUI/QtMUCConfigurationWindow.h b/Swift/QtUI/QtMUCConfigurationWindow.h new file mode 100644 index 0000000..2be126b --- /dev/null +++ b/Swift/QtUI/QtMUCConfigurationWindow.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +class QBoxLayout; + +namespace Swift { + class QtFormWidget; + class QtMUCConfigurationWindow : public QWidget { + Q_OBJECT + public: + QtMUCConfigurationWindow(Form::ref form); + virtual ~QtMUCConfigurationWindow(); + boost::signal onFormComplete; + private slots: + void handleCancelClicked(); + void handleOKClicked(); + private: + QtFormWidget* formWidget_; + QPushButton* okButton_; + QPushButton* cancelButton_; + }; +} diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 1d7ab78..82e4490 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -136,6 +136,7 @@ sources = [ "qrc_DefaultTheme.cc", "qrc_Swift.cc", "QtFileTransferJSBridge.cpp", + "QtMUCConfigurationWindow.cpp", ] myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") diff --git a/Swiften/Elements/MUCOwnerPayload.h b/Swiften/Elements/MUCOwnerPayload.h index a3db05b..5ccc755 100644 --- a/Swiften/Elements/MUCOwnerPayload.h +++ b/Swiften/Elements/MUCOwnerPayload.h @@ -9,6 +9,7 @@ #include #include +#include namespace Swift { class MUCOwnerPayload : public Payload { @@ -26,6 +27,10 @@ namespace Swift { payload = p; } + Form::ref getForm() { + return boost::dynamic_pointer_cast
(payload); + } + private: boost::shared_ptr payload; }; diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp index 43d3f36..d4c1287 100644 --- a/Swiften/MUC/MUC.cpp +++ b/Swiften/MUC/MUC.cpp @@ -245,10 +245,37 @@ void MUC::changeSubject(const std::string& subject) { } void MUC::requestConfigurationForm() { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + GenericRequest* request = new GenericRequest(IQ::Get, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUC::handleConfigurationFormReceived, this, _1, _2)); + request->send(); +} +void MUC::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) { + Form::ref form; + if (payload) { + form = payload->getForm(); + } + if (error || !form) { + onConfigurationFailed(error); + } else { + onConfigurationFormReceived(form); + } } -//FIXME: Recognise Topic changes +void MUC::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) { + if (error) { + onConfigurationFailed(error); + } +} + +void MUC::configureRoom(Form::ref form) { + MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); + mucPayload->setPayload(form); + GenericRequest* request = new GenericRequest(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUC::handleConfigurationResultReceived, this, _1, _2)); + request->send(); +} //TODO: Invites(direct/mediated) diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h index e223de8..4017c97 100644 --- a/Swiften/MUC/MUC.h +++ b/Swiften/MUC/MUC.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -58,15 +59,18 @@ namespace Swift { void kickUser(const JID& jid); void changeSubject(const std::string& subject); void requestConfigurationForm(); + void configureRoom(Form::ref); public: boost::signal onJoinComplete; boost::signal onJoinFailed; boost::signal onKickFailed; + boost::signal onConfigurationFailed; boost::signal onOccupantPresenceChange; boost::signal onOccupantRoleChanged; boost::signal onOccupantAffiliationChanged; boost::signal onOccupantJoined; boost::signal onOccupantLeft; + boost::signal onConfigurationFormReceived; /* boost::signal onInfoResult; */ /* boost::signal onItemsResult; */ @@ -85,6 +89,8 @@ namespace Swift { void internalJoin(const std::string& nick); void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref); void handleKickResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&); + void handleConfigurationFormReceived(MUCOwnerPayload::ref, ErrorPayload::ref); + void handleConfigurationResultReceived(MUCOwnerPayload::ref, ErrorPayload::ref); private: JID ownMUCJID; diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index 9eafcba..3d56f61 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include #include #include #include @@ -102,6 +104,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr(new PrivateStorageParserFactory(this))); factories_.push_back(shared_ptr(new ChatStateParserFactory())); factories_.push_back(shared_ptr(new MUCUserPayloadParserFactory())); + factories_.push_back(shared_ptr(new MUCOwnerPayloadParserFactory(this))); factories_.push_back(shared_ptr(new GenericPayloadParserFactory("query", "http://jabber.org/protocol/muc#admin"))); factories_.push_back(shared_ptr(new NicknameParserFactory())); factories_.push_back(shared_ptr(new JingleParserFactory(this))); diff --git a/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.cpp new file mode 100644 index 0000000..d7ae789 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include +#include +#include + +namespace Swift { + +MUCOwnerPayloadParser::MUCOwnerPayloadParser(PayloadParserFactoryCollection* factories) : factories(factories), level(0) { +} + +void MUCOwnerPayloadParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { + if (level == 1) { + PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (level >= 1 && currentPayloadParser) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + ++level; +} + +void MUCOwnerPayloadParser::handleEndElement(const std::string& element, const std::string& ns) { + --level; + if (currentPayloadParser) { + if (level >= 1) { + currentPayloadParser->handleEndElement(element, ns); + } + + if (level == 1) { + getPayloadInternal()->setPayload(currentPayloadParser->getPayload()); + } + } +} + +void MUCOwnerPayloadParser::handleCharacterData(const std::string& data) { + if (level > 1 && currentPayloadParser) { + currentPayloadParser->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.h new file mode 100644 index 0000000..2761981 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include + +#include +#include + +namespace Swift { + class PayloadParserFactoryCollection; + + class MUCOwnerPayloadParser : public GenericPayloadParser { + public: + MUCOwnerPayloadParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + PayloadParserFactoryCollection* factories; + int level; + boost::shared_ptr currentPayloadParser; + }; +} diff --git a/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParserFactory.h b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParserFactory.h new file mode 100644 index 0000000..918c72c --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCOwnerPayloadParserFactory.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include +#include + +namespace Swift { + class PayloadParserFactoryCollection; + + class MUCOwnerPayloadParserFactory : public PayloadParserFactory { + public: + MUCOwnerPayloadParserFactory(PayloadParserFactoryCollection* factories) : factories(factories) { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return element == "query" && ns == "http://jabber.org/protocol/muc#owner"; + } + + virtual PayloadParser* createPayloadParser() { + return new MUCOwnerPayloadParser(factories); + } + + private: + PayloadParserFactoryCollection* factories; + + }; +} diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 3dbfbee..67f706d 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -61,6 +61,7 @@ sources = [ "PayloadParsers/DelayParser.cpp", "PayloadParsers/MUCUserPayloadParser.cpp", "PayloadParsers/MUCAdminPayloadParser.cpp", + "PayloadParsers/MUCOwnerPayloadParser.cpp", "PayloadParsers/MUCItemParser.cpp", "PayloadParsers/NicknameParser.cpp", "PayloadParsers/ReplaceParser.cpp", -- cgit v0.10.2-6-g49f6