From 522a3a1c66233792741eb47f75e009a2fe9fa3ad Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 18 Nov 2010 17:01:22 +0000 Subject: Initial Ad-Hoc commands support. This is not finished, use at your own peril. diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp new file mode 100644 index 0000000..368771f --- /dev/null +++ b/Swift/Controllers/AdHocManager.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Swift { + +AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow) : jid_(jid) { + iqRouter_ = iqRouter; + uiEventStream_ = uiEventStream; + mainWindow_ = mainWindow; + factory_ = factory; + + uiEventStream_->onUIEvent.connect(boost::bind(&AdHocManager::handleUIEvent, this, _1)); +} + +AdHocManager::~AdHocManager() { + +} + +void AdHocManager::setServerDiscoInfo(boost::shared_ptr info) { + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::CommandsFeature)) { + GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(JID(jid_.getDomain()), DiscoInfo::CommandsFeature, iqRouter_); + discoItemsRequest->onResponse.connect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2)); + discoItemsRequest->send(); + } else { + mainWindow_->setAvailableAdHocCommands(std::vector()); + } + +} + +void AdHocManager::handleServerDiscoItemsResponse(boost::shared_ptr items, ErrorPayload::ref error) { + std::vector commands; + if (!error) { + foreach (DiscoItems::Item item, items->getItems()) { + if (item.getNode() != "http://isode.com/xmpp/commands#test") { + commands.push_back(item); + } + } + } + mainWindow_->setAvailableAdHocCommands(commands); +} + +void AdHocManager::handleUIEvent(boost::shared_ptr event) { + boost::shared_ptr adHocEvent = boost::dynamic_pointer_cast(event); + if (adHocEvent) { + factory_->createAdHocCommandWindow(boost::shared_ptr(new OutgoingAdHocCommandSession(adHocEvent->getCommand(), factory_, iqRouter_))); + } +} + +} diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h new file mode 100644 index 0000000..9e21bf7 --- /dev/null +++ b/Swift/Controllers/AdHocManager.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-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 +#include +#include + +namespace Swift { + class IQRouter; + class MainWindow; + class UIEventStream; + class AdHocCommandWindowFactory; + class AdHocManager { + public: + AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow); + ~AdHocManager(); + void setServerDiscoInfo(boost::shared_ptr info); + private: + void handleUIEvent(boost::shared_ptr event); + void handleServerDiscoItemsResponse(boost::shared_ptr, ErrorPayload::ref error); + JID jid_; + IQRouter* iqRouter_; + UIEventStream* uiEventStream_; + MainWindow* mainWindow_; + AdHocCommandWindowFactory* factory_; + }; +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 3f86b43..efe4de4 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -21,7 +21,6 @@ #include "Swift/Controllers/StoragesFactory.h" #include "Swiften/Client/Storages.h" #include "Swiften/VCards/VCardManager.h" -#include "Swift/Controllers/Chat/MUCSearchController.h" #include "Swift/Controllers/Chat/UserSearchController.h" #include "Swift/Controllers/Chat/ChatsManager.h" #include "Swift/Controllers/XMPPEvents/EventController.h" @@ -66,6 +65,7 @@ #include #include #include +#include "Swift/Controllers/AdHocManager.h" namespace Swift { @@ -268,14 +268,14 @@ void MainController::handleConnected() { client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); - userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); + adHocManager_ = new AdHocManager(boundJID_, uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); } client_->requestRoster(); - GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client_->getIQRouter()); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(boundJID_.toBare(), client_->getIQRouter()); discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2)); discoInfoRequest->send(); @@ -560,6 +560,7 @@ void MainController::setManagersOffline() { void MainController::handleServerDiscoInfoResponse(boost::shared_ptr info, ErrorPayload::ref error) { if (!error) { chatsManager_->setServerDiscoInfo(info); + adHocManager_->setServerDiscoInfo(info); } } diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index f9722de..ae69e70 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -64,6 +64,8 @@ namespace Swift { class NetworkFactories; class URIHandler; class XMPPURIController; + class AdHocManager; + class AdHocCommandWindowFactory; class MainController { public: @@ -133,6 +135,7 @@ namespace Swift { RosterController* rosterController_; EventController* eventController_; EventWindowController* eventWindowController_; + AdHocManager* adHocManager_; LoginWindow* loginWindow_; UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index eb5d21f..bd18a3e 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -44,6 +44,7 @@ if env["SCONS_STAGE"] == "build" : "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", + "AdHocManager.cpp", "XMPPEvents/EventController.cpp", "UIEvents/UIEvent.cpp", "UIInterfaces/XMLConsoleWidget.cpp", diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h new file mode 100644 index 0000000..c3b4b49 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 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 RequestAdHocUIEvent : public UIEvent { + public: + RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {}; + const DiscoItems::Item& getCommand() const {return command_;} + private: + DiscoItems::Item command_; + }; +} diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h new file mode 100644 index 0000000..f7a5d39 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class AdHocCommandWindow { + public: + virtual ~AdHocCommandWindow() {}; + }; +} diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h new file mode 100644 index 0000000..ae77180 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010-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 AdHocCommandWindowFactory { + public: + virtual ~AdHocCommandWindowFactory() {} + /** + * The UI should deal with the lifetime of this window (i.e. DeleteOnClose), + * so the result isn't returned. + */ + virtual void createAdHocCommandWindow(boost::shared_ptr command) = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 2fd463b..93584e7 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -9,6 +9,7 @@ #include #include "Swiften/JID/JID.h" #include "Swiften/Elements/StatusShow.h" +#include "Swiften/Elements/DiscoItems.h" #include "Swiften/Base/boost_bsignals.h" #include @@ -33,6 +34,7 @@ namespace Swift { /** Must be able to cope with NULL to clear the roster */ virtual void setRosterModel(Roster* roster) = 0; virtual void setConnecting() = 0; + virtual void setAvailableAdHocCommands(const std::vector& commands) = 0; boost::signal onChangeStatusRequest; boost::signal onSignOutRequest; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 9b36ac5..57f55d0 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace Swift { class UIFactory : @@ -30,7 +31,8 @@ namespace Swift { public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, - public ContactEditWindowFactory { + public ContactEditWindowFactory, + public AdHocCommandWindowFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index afa5c2a..f773062 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -20,6 +20,7 @@ namespace Swift { virtual void setMyAvatarPath(const std::string& /*path*/) {}; virtual void setMyStatusText(const std::string& /*status*/) {}; virtual void setMyStatusType(StatusShow::Type /*type*/) {}; + virtual void setAvailableAdHocCommands(const std::vector& /*commands*/) {}; virtual void setConnecting() {}; Roster* roster; diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp new file mode 100644 index 0000000..f6b1b5c --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWindow.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010-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 { +QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr command) : command_(command) { + + formWidget_ = NULL; + + setAttribute(Qt::WA_DeleteOnClose); + command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1)); + command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1)); + command->start(); + + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(2); + label_ = new QLabel(this); + layout->addWidget(label_); + QWidget* formContainer = new QWidget(this); + layout->addWidget(formContainer); + formLayout_ = new QBoxLayout(QBoxLayout::TopToBottom, formContainer); + 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())); + backButton_ = new QPushButton(tr("Back"), buttonsWidget); + buttonsLayout->addWidget(backButton_); + connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked())); + nextButton_ = new QPushButton(tr("Next"), buttonsWidget); + buttonsLayout->addWidget(nextButton_); + connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked())); + completeButton_ = new QPushButton(tr("Complete"), buttonsWidget); + buttonsLayout->addWidget(completeButton_); + connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked())); + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + show(); +} + +QtAdHocCommandWindow::~QtAdHocCommandWindow() { + +} + +void QtAdHocCommandWindow::handleCancelClicked() { + command_->cancel(); +} + +void QtAdHocCommandWindow::handlePrevClicked() { + command_->goBack(); +} + +void QtAdHocCommandWindow::handleNextClicked() { + command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); +} + +void QtAdHocCommandWindow::handleCompleteClicked() { + command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); +} + +void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) { + if (command->getForm()) { + setForm(command->getForm()); + } else { + setNoForm(); + } + QString notes; + foreach (Command::Note note, command->getNotes()) { + if (!notes.isEmpty()) { + notes += "\n"; + QString qNote(note.note.c_str()); + switch (note.type) { + case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break; + case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break; + case Command::Note::Info: notes += qNote; break; + } + } + } + label_->setText(notes); + setAvailableActions(command); +} + +void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) { + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + label_->setText(tr("Error executing command")); +} + +void QtAdHocCommandWindow::setForm(Form::ref form) { + delete formWidget_; + formWidget_ = new QtFormWidget(form, this); + formLayout_->addWidget(formWidget_); +} + +void QtAdHocCommandWindow::setNoForm() { + delete formWidget_; +} + +void QtAdHocCommandWindow::setAvailableActions(Command::ref commandResult) { + const std::vector actions = commandResult->getAvailableActions(); + nextButton_->setEnabled(std::find(actions.begin(), actions.end(), Command::Next) != actions.end()); + backButton_->setEnabled(std::find(actions.begin(), actions.end(), Command::Prev) != actions.end()); + completeButton_->setEnabled(std::find(actions.begin(), actions.end(), Command::Complete) != actions.end()); + + + if (command_->getIsMultiStage()) { + backButton_->show(); + nextButton_->show(); + } + else { + backButton_->hide(); + nextButton_->hide(); + + } +} + +} diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h new file mode 100644 index 0000000..f1073bc --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWindow.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-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 QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow { + Q_OBJECT + public: + QtAdHocCommandWindow(boost::shared_ptr command); + virtual ~QtAdHocCommandWindow(); + private: + void handleNextStageReceived(Command::ref command); + void handleError(ErrorPayload::ref error); + void setForm(Form::ref); + void setNoForm(); + void setAvailableActions(Command::ref commandResult); + private slots: + void handleCancelClicked(); + void handlePrevClicked(); + void handleNextClicked(); + void handleCompleteClicked(); + private: + boost::shared_ptr command_; + QtFormWidget* formWidget_; + QBoxLayout* formLayout_; + Form::ref form_; + QLabel* label_; + QPushButton* backButton_; + QPushButton* nextButton_; + QPushButton* completeButton_; + QPushButton* cancelButton_; + }; +} diff --git a/Swift/QtUI/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp new file mode 100644 index 0000000..659fb25 --- /dev/null +++ b/Swift/QtUI/QtFormWidget.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Swift { + +QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), form_(form) { + QGridLayout* thisLayout = new QGridLayout(this); + int row = 0; + if (!form->getTitle().empty()) { + QLabel* instructions = new QLabel(("" + form->getTitle() + "").c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + if (!form->getInstructions().empty()) { + QLabel* instructions = new QLabel(form->getInstructions().c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + QScrollArea* scrollArea = new QScrollArea(this); + thisLayout->addWidget(scrollArea); + QWidget* scroll = new QWidget(this); + QGridLayout* layout = new QGridLayout(scroll); + foreach (boost::shared_ptr field, form->getFields()) { + QWidget* widget = createWidget(field); + if (widget) { + layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0); + layout->addWidget(widget, row++, 1); + } + } + scrollArea->setWidget(scroll); + scrollArea->setWidgetResizable(true); +} + +QtFormWidget::~QtFormWidget() { + +} + +QListWidget* QtFormWidget::createList(FormField::ref field) { + QListWidget* listWidget = new QListWidget(this); + listWidget->setSortingEnabled(false); + listWidget->setSelectionMode(boost::dynamic_pointer_cast(field) ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection); + foreach (FormField::Option option, field->getOptions()) { + listWidget->addItem(option.label.c_str()); + } + boost::shared_ptr listMultiField = boost::dynamic_pointer_cast(field); + boost::shared_ptr listSingleField = boost::dynamic_pointer_cast(field); + for (int i = 0; i < listWidget->count(); i++) { + QListWidgetItem* item = listWidget->item(i); + bool selected = false; + if (listSingleField) { + selected = (item->text() == QString(listSingleField->getValue().c_str())); + } + else if (listMultiField) { + std::string text = Q2PSTRING(item->text()); + selected = (std::find(listMultiField->getValue().begin(), listMultiField->getValue().end(), text) != listMultiField->getValue().end()); + } + item->setSelected(selected); + } + return listWidget; +} + +QWidget* QtFormWidget::createWidget(FormField::ref field) { + QWidget* widget = NULL; + boost::shared_ptr booleanField = boost::dynamic_pointer_cast(field); + if (booleanField) { + QCheckBox* checkWidget = new QCheckBox(this); + checkWidget->setCheckState(booleanField->getValue() ? Qt::Checked : Qt::Unchecked); + widget = checkWidget; + } + boost::shared_ptr fixedField = boost::dynamic_pointer_cast(field); + if (fixedField) { + QString value = fixedField->getValue().c_str(); + widget = new QLabel(value, this); + } + boost::shared_ptr listSingleField = boost::dynamic_pointer_cast(field); + if (listSingleField) { + widget = createList(field); + } + boost::shared_ptr textMultiField = boost::dynamic_pointer_cast(field); + if (textMultiField) { + QString value = textMultiField->getValue().c_str(); + widget = new QTextEdit(value, this); + } + boost::shared_ptr textPrivateField = boost::dynamic_pointer_cast(field); + if (textPrivateField) { + QString value = textPrivateField->getValue().c_str(); + QLineEdit* lineWidget = new QLineEdit(value, this); + lineWidget->setEchoMode(QLineEdit::Password); + widget = lineWidget; + } + boost::shared_ptr textSingleField = boost::dynamic_pointer_cast(field); + if (textSingleField) { + QString value = textSingleField->getValue().c_str(); + widget = new QLineEdit(value, this); + } + boost::shared_ptr jidSingleField = boost::dynamic_pointer_cast(field); + if (jidSingleField) { + QString value = jidSingleField->getValue().toString().c_str(); + widget = new QLineEdit(value, this); + } + boost::shared_ptr jidMultiField = boost::dynamic_pointer_cast(field); + if (jidMultiField) { + QString text; + bool prev = false; + foreach (JID line, jidMultiField->getValue()) { + if (prev) { + text += "\n"; + } + prev = true; + text += line.toString().c_str(); + } + widget = new QTextEdit(text, this); + } + boost::shared_ptr listMultiField = boost::dynamic_pointer_cast(field); + if (listMultiField) { + widget = createList(field); + } + boost::shared_ptr untypedField = boost::dynamic_pointer_cast(field); + if (untypedField) { + } + boost::shared_ptr hiddenField = boost::dynamic_pointer_cast(field); + if (hiddenField) { + } + fields_[field->getName()] = widget; + return widget; +} + +Form::ref QtFormWidget::getCompletedForm() { + Form::ref result(new Form(Form::SubmitType)); + foreach (boost::shared_ptr field, form_->getFields()) { + boost::shared_ptr resultField; + boost::shared_ptr booleanField = boost::dynamic_pointer_cast(field); + if (booleanField) { + resultField = FormField::ref(BooleanFormField::create(qobject_cast(fields_[field->getName()])->checkState() == Qt::Checked)); + } + boost::shared_ptr fixedField = boost::dynamic_pointer_cast(field); + if (fixedField) { + resultField = FormField::ref(FixedFormField::create(fixedField->getValue())); + } + boost::shared_ptr listSingleField = boost::dynamic_pointer_cast(field); + if (listSingleField) { + QListWidget* listWidget = qobject_cast(fields_[field->getName()]); + if (listWidget->selectedItems().size() > 0) { + int i = listWidget->row(listWidget->selectedItems()[0]); + resultField = FormField::ref(ListSingleFormField::create(field->getOptions()[i].value)); + } + else { + resultField = FormField::ref(ListSingleFormField::create()); + } + } + boost::shared_ptr textMultiField = boost::dynamic_pointer_cast(field); + if (textMultiField) { + QTextEdit* widget = qobject_cast(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (string.isEmpty()) { + resultField = FormField::ref(TextMultiFormField::create()); + } + else { + resultField = FormField::ref(TextMultiFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr textPrivateField = boost::dynamic_pointer_cast(field); + if (textPrivateField) { + QLineEdit* widget = qobject_cast(fields_[field->getName()]); + QString string = widget->text(); + if (string.isEmpty()) { + resultField = FormField::ref(TextPrivateFormField::create()); + } + else { + resultField = FormField::ref(TextPrivateFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr textSingleField = boost::dynamic_pointer_cast(field); + if (textSingleField) { + QLineEdit* widget = qobject_cast(fields_[field->getName()]); + QString string = widget->text(); + if (string.isEmpty()) { + resultField = FormField::ref(TextSingleFormField::create()); + } + else { + resultField = FormField::ref(TextSingleFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr jidSingleField = boost::dynamic_pointer_cast(field); + if (jidSingleField) { + QLineEdit* widget = qobject_cast(fields_[field->getName()]); + QString string = widget->text(); + JID jid(Q2PSTRING(string)); + if (string.isEmpty()) { + resultField = FormField::ref(JIDSingleFormField::create()); + } + else { + resultField = FormField::ref(JIDSingleFormField::create(jid)); + } + } + boost::shared_ptr jidMultiField = boost::dynamic_pointer_cast(field); + if (jidMultiField) { + QTextEdit* widget = qobject_cast(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (string.isEmpty()) { + resultField = FormField::ref(JIDMultiFormField::create()); + } + else { + QStringList lines = string.split("\n"); + std::vector value; + foreach (QString line, lines) { + value.push_back(JID(Q2PSTRING(line))); + } + resultField = FormField::ref(JIDMultiFormField::create(value)); + } + } + boost::shared_ptr listMultiField = boost::dynamic_pointer_cast(field); + if (listMultiField) { + QListWidget* listWidget = qobject_cast(fields_[field->getName()]); + std::vector values; + foreach (QListWidgetItem* item, listWidget->selectedItems()) { + values.push_back(field->getOptions()[listWidget->row(item)].value); + } + resultField = FormField::ref(ListMultiFormField::create(values)); + } + boost::shared_ptr untypedField = boost::dynamic_pointer_cast(field); + if (untypedField) { + } + boost::shared_ptr hiddenField = boost::dynamic_pointer_cast(field); + if (hiddenField) { + resultField = FormField::ref(HiddenFormField::create(hiddenField->getValue())); + } + resultField->setName(field->getName()); + result->addField(resultField); + } + return result; +} + +} diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h new file mode 100644 index 0000000..2fb7b98 --- /dev/null +++ b/Swift/QtUI/QtFormWidget.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 + +class QListWidget; + +namespace Swift { + +class QtFormWidget : public QWidget { + Q_OBJECT + public: + QtFormWidget(Form::ref form, QWidget* parent = NULL); + virtual ~QtFormWidget(); + Form::ref getCompletedForm(); + private: + QWidget* createWidget(FormField::ref field); + QListWidget* createList(FormField::ref field); + std::map fields_; + Form::ref form_; +}; + +} diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 6391961..f7dba58 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -30,6 +30,7 @@ #include "Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestAdHocUIEvent.h" namespace Swift { @@ -99,6 +100,8 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS chatUserAction_ = new QAction(tr("Start &Chat"), this); connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); actionsMenu->addAction(chatUserAction_); + serverAdHocMenu_ = new QMenu("Server Commands", this); + actionsMenu->addMenu(serverAdHocMenu_); actionsMenu->addSeparator(); QAction* signOutAction = new QAction(tr("&Sign Out"), this); connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); @@ -106,6 +109,12 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool))); + setAvailableAdHocCommands(std::vector()); + QAction* adHocAction = new QAction("Populating commands..", this); + adHocAction->setEnabled(false); + serverAdHocMenu_->addAction(adHocAction); + serverAdHocCommandActions_.append(adHocAction); + lastOfflineState_ = false; uiEventStream_->onUIEvent.connect(boost::bind(&QtMainWindow::handleUIEvent, this, _1)); } @@ -206,6 +215,33 @@ void QtMainWindow::setConnecting() { meView_->setConnecting(); } +void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) { + QAction* action = qobject_cast(sender()); + assert(action); + DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)]; + uiEventStream_->send(boost::shared_ptr(new RequestAdHocUIEvent(command))); +} + +void QtMainWindow::setAvailableAdHocCommands(const std::vector& commands) { + serverAdHocCommands_ = commands; + foreach (QAction* action, serverAdHocCommandActions_) { + delete action; + } + serverAdHocMenu_->clear(); + serverAdHocCommandActions_.clear(); + foreach (DiscoItems::Item command, commands) { + QAction* action = new QAction(P2QSTRING(command.getName()), this); + connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool))); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } + if (serverAdHocCommandActions_.isEmpty()) { + QAction* action = new QAction("No available commands", this); + action->setEnabled(false); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } +} } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 3462bb0..e20d773 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -8,6 +8,7 @@ #include #include +#include #include "Swift/Controllers/UIInterfaces/MainWindow.h" #include "Swift/QtUI/QtRosterHeader.h" #include "Swift/QtUI/EventViewer/QtEventWindow.h" @@ -20,7 +21,7 @@ class QLineEdit; class QPushButton; class QToolBar; class QAction; - +class QMenu; class QTabWidget; namespace Swift { @@ -46,6 +47,7 @@ namespace Swift { QtEventWindow* getEventWindow(); QtChatListWindow* getChatListWindow(); void setRosterModel(Roster* roster); + void setAvailableAdHocCommands(const std::vector& commands); private slots: void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage); void handleUIEvent(boost::shared_ptr event); @@ -55,6 +57,7 @@ namespace Swift { void handleEditProfileAction(); void handleAddUserActionTriggered(bool checked); void handleChatUserActionTriggered(bool checked); + void handleAdHocActionTriggered(bool checked); void handleEventCountUpdated(int count); void handleEditProfileRequest(); @@ -66,6 +69,7 @@ namespace Swift { QAction* editUserAction_; QAction* chatUserAction_; QAction* showOfflineAction_; + QMenu* serverAdHocMenu_; QtTabWidget* tabs_; QWidget* contactsTabWidget_; QWidget* eventsTabWidget_; @@ -73,5 +77,7 @@ namespace Swift { QtChatListWindow* chatListWindow_; UIEventStream* uiEventStream_; bool lastOfflineState_; + std::vector serverAdHocCommands_; + QList serverAdHocCommandActions_; }; } diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 35fbfcd..5c1f78d 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -23,6 +23,7 @@ #include "UserSearch/QtUserSearchWindow.h" #include "QtProfileWindow.h" #include "QtContactEditWindow.h" +#include "QtAdHocCommandWindow.h" namespace Swift { @@ -99,5 +100,8 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() { return new QtContactEditWindow(); } +void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr command) { + new QtAdHocCommandWindow(command); +} } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index ddaaf6e..9ef228a 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -37,6 +37,7 @@ namespace Swift { virtual JoinMUCWindow* createJoinMUCWindow(); virtual ProfileWindow* createProfileWindow(); virtual ContactEditWindow* createContactEditWindow(); + virtual void createAdHocCommandWindow(boost::shared_ptr command); private slots: void handleLoginWindowGeometryChanged(); diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 58f2b99..2f138cd 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -88,6 +88,7 @@ sources = [ "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtAdHocCommandWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", "QtAddBookmarkWindow.cpp", @@ -98,6 +99,7 @@ sources = [ "MessageSnippet.cpp", "SystemMessageSnippet.cpp", "QtElidingLabel.cpp", + "QtFormWidget.cpp", "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", "Roster/RosterModel.cpp", diff --git a/Swiften/AdHoc/OutgoingAdHocCommandSession.cpp b/Swiften/AdHoc/OutgoingAdHocCommandSession.cpp new file mode 100644 index 0000000..40b17e7 --- /dev/null +++ b/Swiften/AdHoc/OutgoingAdHocCommandSession.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include + +#include + +#include + +namespace Swift { +OutgoingAdHocCommandSession::OutgoingAdHocCommandSession(const DiscoItems::Item& command, AdHocCommandWindowFactory* /*factory*/, IQRouter* iqRouter) : command_(command), iqRouter_(iqRouter), isMultiStage_(false) { + +} + +void OutgoingAdHocCommandSession::handleResponse(boost::shared_ptr payload, ErrorPayload::ref error) { + if (error) { + onError(error); + } else { + sessionID_ = payload->getSessionID(); + const std::vector actions = payload->getAvailableActions(); + if (std::find(actions.begin(), actions.end(), Command::Next) != actions.end() + || std::find(actions.begin(), actions.end(), Command::Prev) != actions.end()) { + isMultiStage_ = true; + } + onNextStageReceived(payload); + } +} + +bool OutgoingAdHocCommandSession::getIsMultiStage() { + return isMultiStage_; +} + +void OutgoingAdHocCommandSession::start() { + boost::shared_ptr commandPayload(new Command(command_.getNode())); + boost::shared_ptr > commandRequest(new GenericRequest(IQ::Set, command_.getJID(), commandPayload, iqRouter_)); + commandRequest->onResponse.connect(boost::bind(&OutgoingAdHocCommandSession::handleResponse, this, _1, _2)); + commandRequest->send(); +} + +void OutgoingAdHocCommandSession::cancel() { + if (!sessionID_.empty()) { + boost::shared_ptr commandPayload(new Command(command_.getNode(), sessionID_, Command::Cancel)); + boost::shared_ptr > commandRequest(new GenericRequest(IQ::Set, command_.getJID(), commandPayload, iqRouter_)); + commandRequest->onResponse.connect(boost::bind(&OutgoingAdHocCommandSession::handleResponse, this, _1, _2)); + commandRequest->send(); + } +} + +void OutgoingAdHocCommandSession::goBack() { + boost::shared_ptr commandPayload(new Command(command_.getNode(), sessionID_, Command::Prev)); + boost::shared_ptr > commandRequest(new GenericRequest(IQ::Set, command_.getJID(), commandPayload, iqRouter_)); + commandRequest->onResponse.connect(boost::bind(&OutgoingAdHocCommandSession::handleResponse, this, _1, _2)); + commandRequest->send(); +} + +void OutgoingAdHocCommandSession::complete(Form::ref form) { + Command* command = new Command(command_.getNode(), sessionID_, Command::Complete); + boost::shared_ptr commandPayload(command); + command->setForm(form); + boost::shared_ptr > commandRequest(new GenericRequest(IQ::Set, command_.getJID(), commandPayload, iqRouter_)); + commandRequest->onResponse.connect(boost::bind(&OutgoingAdHocCommandSession::handleResponse, this, _1, _2)); + commandRequest->send(); +} + +void OutgoingAdHocCommandSession::goNext(Form::ref form) { + Command* command = new Command(command_.getNode(), sessionID_, Command::Next); + boost::shared_ptr commandPayload(command); + command->setForm(form); + boost::shared_ptr > commandRequest(new GenericRequest(IQ::Set, command_.getJID(), commandPayload, iqRouter_)); + commandRequest->onResponse.connect(boost::bind(&OutgoingAdHocCommandSession::handleResponse, this, _1, _2)); + commandRequest->send(); +} + +} diff --git a/Swiften/AdHoc/OutgoingAdHocCommandSession.h b/Swiften/AdHoc/OutgoingAdHocCommandSession.h new file mode 100644 index 0000000..820dc62 --- /dev/null +++ b/Swiften/AdHoc/OutgoingAdHocCommandSession.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010-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 + +namespace Swift { + class IQRouter; + class MainWindow; + class UIEventStream; + class AdHocCommandWindowFactory; + class OutgoingAdHocCommandSession { + public: + OutgoingAdHocCommandSession(const DiscoItems::Item& command, AdHocCommandWindowFactory* factory, IQRouter* iqRouter); + /** + * Send initial request to the target. + */ + void start(); + /** + * Cancel command session with the target. + */ + void cancel(); + /** + * Return to the previous stage. + */ + void goBack(); + /** + * Send the form to complete the command. + * \param form Form for submission - if missing the command will be submitted with no form. + */ + void complete(Form::ref form); + /** + * Send the form to advance to the next stage of the command. + * \param form Form for submission - if missing the command will be submitted with no form. + */ + void goNext(Form::ref form); + + /** + * Is the form multi-stage? + */ + bool getIsMultiStage(); + + /** + * Emitted when the form for the next stage is available. + */ + boost::signal onNextStageReceived; + + /** + * Emitted on error. + */ + boost::signal onError; + private: + void handleResponse(boost::shared_ptr payload, ErrorPayload::ref error); + private: + DiscoItems::Item command_; + IQRouter* iqRouter_; + bool isMultiStage_; + std::string sessionID_; + }; +} diff --git a/Swiften/AdHoc/SConscript b/Swiften/AdHoc/SConscript new file mode 100644 index 0000000..69c9083 --- /dev/null +++ b/Swiften/AdHoc/SConscript @@ -0,0 +1,6 @@ +Import("swiften_env") + +objects = swiften_env.SwiftenObject([ + "OutgoingAdHocCommandSession.cpp", + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Disco/GetDiscoItemsRequest.h b/Swiften/Disco/GetDiscoItemsRequest.h index 0a94402..46735ef 100644 --- a/Swiften/Disco/GetDiscoItemsRequest.h +++ b/Swiften/Disco/GetDiscoItemsRequest.h @@ -18,9 +18,18 @@ namespace Swift { return ref(new GetDiscoItemsRequest(jid, router)); } + static ref create(const JID& jid, const std::string& node, IQRouter* router) { + return ref(new GetDiscoItemsRequest(jid, node, router)); + } + private: GetDiscoItemsRequest(const JID& jid, IQRouter* router) : GenericRequest(IQ::Get, jid, boost::shared_ptr(new DiscoItems()), router) { } + + GetDiscoItemsRequest(const JID& jid, const std::string& node, IQRouter* router) : + GenericRequest(IQ::Get, jid, boost::shared_ptr(new DiscoItems()), router) { + getPayloadGeneric()->setNode(node); + } }; } diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp index 158ea66..5b2bb04 100644 --- a/Swiften/Elements/DiscoInfo.cpp +++ b/Swiften/Elements/DiscoInfo.cpp @@ -14,6 +14,7 @@ const std::string DiscoInfo::ChatStatesFeature = std::string("http://jabber.org/ const std::string DiscoInfo::SecurityLabelsFeature = std::string("urn:xmpp:sec-label:0"); const std::string DiscoInfo::SecurityLabelsCatalogFeature = std::string("urn:xmpp:sec-label:catalog:2"); const std::string DiscoInfo::JabberSearchFeature = std::string("jabber:iq:search"); +const std::string DiscoInfo::CommandsFeature = std::string("http://jabber.org/protocol/commands"); bool DiscoInfo::Identity::operator<(const Identity& other) const { diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index 6144b16..cd650b9 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -21,6 +21,7 @@ namespace Swift { static const std::string SecurityLabelsFeature; static const std::string SecurityLabelsCatalogFeature; static const std::string JabberSearchFeature; + static const std::string CommandsFeature; class Identity { public: diff --git a/Swiften/SConscript b/Swiften/SConscript index 7c1f70f..5536b3f 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -190,6 +190,7 @@ if env["SCONS_STAGE"] == "build" : "StreamManagement", "Component", "Config", + "AdHoc" ]) SConscript(test_only = True, dirs = [ "QA", diff --git a/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp b/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp index 0fa45ce..12a38a8 100644 --- a/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp @@ -21,7 +21,7 @@ CommandSerializer::CommandSerializer() { } std::string CommandSerializer::serializePayload(boost::shared_ptr command) const { - XMLElement commandElement("command", "http://jabber.org/protocol/comands"); + XMLElement commandElement("command", "http://jabber.org/protocol/commands"); commandElement.setAttribute("node", command->getNode()); if (!command->getSessionID().empty()) { -- cgit v0.10.2-6-g49f6