diff options
Diffstat (limited to 'Swift/QtUI/QtPlainChatView.cpp')
-rw-r--r-- | Swift/QtUI/QtPlainChatView.cpp | 340 |
1 files changed, 330 insertions, 10 deletions
diff --git a/Swift/QtUI/QtPlainChatView.cpp b/Swift/QtUI/QtPlainChatView.cpp index 98d2e8b..ee76438 100644 --- a/Swift/QtUI/QtPlainChatView.cpp +++ b/Swift/QtUI/QtPlainChatView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Kevin Smith + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,29 +7,40 @@ #include <Swift/QtUI/QtPlainChatView.h> #include <QTextEdit> +#include <QScrollBar> #include <QVBoxLayout> +#include <QPushButton> +#include <QLabel> +#include <QDialog> +#include <QProgressBar> +#include <QFileDialog> +#include <QInputDialog> +#include <QMenu> #include <Swiften/Base/foreach.h> +#include <Swiften/Base/FileSize.h> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/QtUI/ChatSnippet.h> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUtilities.h> - namespace Swift { -QtPlainChatView::QtPlainChatView(QWidget* parent) : QtChatView(parent) { +QtPlainChatView::QtPlainChatView(QtChatWindow *window, UIEventStream* eventStream) +: QtChatView(window), window_(window), eventStream_(eventStream), idGenerator_(0) { QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); - log_ = new QTextEdit(this); + log_ = new LogTextEdit(this); log_->setReadOnly(true); log_->setAccessibleName(tr("Chat Messages")); mainLayout->addWidget(log_); } QtPlainChatView::~QtPlainChatView() { - } QString chatMessageToString(const ChatWindow::ChatMessage& message) { @@ -73,8 +84,10 @@ std::string QtPlainChatView::addMessage(const ChatWindow::ChatMessage& message, text += chatMessageToString(message); text += "</p>"; log_->append(text); - return ""; -}; + const std::string idx = senderIsSelf ? "" : senderName; + lastMessageLabel_[idx] = label; + return idx; +} std::string QtPlainChatView::addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& /*avatarPath*/, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) { QString text = "<p>"; @@ -82,12 +95,319 @@ std::string QtPlainChatView::addAction(const ChatWindow::ChatMessage& message, c text += P2QSTRING(label->getLabel()) + "<br/>"; } QString name = senderIsSelf ? "you" : P2QSTRING(senderName); - text += QString(tr("At %1 %2 ")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name); + text += QString(tr("At %1 <i>%2 ")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name); + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); + const std::string idx = senderIsSelf ? "" : senderName; + lastMessageLabel_[idx] = label; + return idx; +} + +void QtPlainChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::addErrorMessage(const ChatWindow::ChatMessage& message) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) +{ + QString text = "<p>"; + if (lastMessageLabel_[id]) { + text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>"; + } + QString name = id.empty() ? "you" : P2QSTRING(id); + text += QString(tr("At %1 %2 corrected the last message to:")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name) + "<br/>"; + text += chatMessageToString(message); + text += "</p>"; + log_->append(text); +} + +void QtPlainChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) +{ + QString text = "<p>"; + if (lastMessageLabel_[id]) { + text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>"; + } + QString name = id.empty() ? "you" : P2QSTRING(id); + text += QString(tr("At %1 %2 corrected the last action to: <i>")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name); + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::replaceLastMessage(const ChatWindow::ChatMessage& message) +{ + QString text = "<p>The last message was corrected to:<br/>"; text += chatMessageToString(message); text += "</p>"; log_->append(text); - return ""; -}; +} + +void QtPlainChatView::setAckState(const std::string& /*id*/, ChatWindow::AckState state) +{ + if (state == ChatWindow::Failed) { + addSystemMessage(ChatWindow::ChatMessage("Message delivery failed due to disconnection from server."), ChatWindow::DefaultDirection); + } +} + +std::string QtPlainChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) +{ + const std::string ftId = "ft" + boost::lexical_cast<std::string>(idGenerator_++); + const std::string sizeString = formatSize(sizeInBytes); + FileTransfer* transfer; + if (senderIsSelf) { + QString description = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, ""); + /* NOTE: it is not possible to abort if description is not provided, since we must always return a valid transfer id */ + const std::string message = std::string() + "Confirm file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>"; + transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, Q2PSTRING(description), message, true); + addSystemMessage(ChatWindow::ChatMessage("Preparing to start file transfer..."), ChatWindow::DefaultDirection); + } else { /* incoming transfer */ + const std::string message = std::string() + "Incoming file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>"; + transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, "", message, true); + addSystemMessage("Incoming file transfer from " + senderName + "...", ChatWindow::DefaultDirection); + } + + fileTransfers_[ftId] = transfer; + layout()->addWidget(transfer->dialog_); + + return ftId; +} + +void QtPlainChatView::setFileTransferProgress(std::string id, const int percentageDone) +{ + FileTransferMap::iterator transfer = fileTransfers_.find(id); + if (transfer != fileTransfers_.end()) { + transfer->second->bar_->setValue(percentageDone); + } +} + +void QtPlainChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) +{ + FileTransferMap::iterator transferIter = fileTransfers_.find(id); + if (transferIter == fileTransfers_.end()) { + return; + } + + /* store the layout index so we can restore it to the same location */ + FileTransfer* oldTransfer = transferIter->second; + const int layoutIndex = layout()->indexOf(oldTransfer->dialog_); + layout()->removeWidget(oldTransfer->dialog_); + const std::string &label = (!msg.empty() ? msg : oldTransfer->message_); + FileTransfer* transfer = new FileTransfer(this, oldTransfer->senderIsSelf_, oldTransfer->ftId_, oldTransfer->filename_, state, oldTransfer->description_, label, false); + fileTransfers_[oldTransfer->ftId_] = transfer; /* replace the transfer object for this file id */ + delete oldTransfer; + + /* insert the new dialog at the old position in the layout list */ + QBoxLayout* parentLayout = dynamic_cast<QBoxLayout*>(layout()); + assert(parentLayout); + parentLayout->insertWidget(layoutIndex, transfer->dialog_); + + /* log the transfer end result as a system message */ + if (state == ChatWindow::Finished) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer completed successfully."), ChatWindow::DefaultDirection); + } else if (state == ChatWindow::Canceled) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer was canceled."), ChatWindow::DefaultDirection); + } else if (state == ChatWindow::FTFailed) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer failed."), ChatWindow::DefaultDirection); + } +} + +void QtPlainChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& /*reason*/, const std::string& password, bool /*direct*/, bool isImpromptu, bool isContinuation) +{ + PopupDialog *invite = new PopupDialog(this); + + QLabel* statusLabel = new QLabel; + std::string msg = senderName + " has invited you to join " + jid.toString() + "..."; + statusLabel->setText(P2QSTRING(msg)); + invite->layout_->addWidget(statusLabel); + invite->layout_->addWidget(new QLabel); /* padding */ + + AcceptMUCInviteAction* accept = new AcceptMUCInviteAction(invite, "Accept", jid, senderName, password, isImpromptu, isContinuation); + connect(accept, SIGNAL(clicked()), SLOT(acceptMUCInvite())); + invite->layout_->addWidget(accept); + + AcceptMUCInviteAction* reject = new AcceptMUCInviteAction(invite, "Reject", jid, senderName, password, isImpromptu, isContinuation); + connect(reject, SIGNAL(clicked()), SLOT(rejectMUCInvite())); + invite->layout_->addWidget(reject); + statusLabel->setText(P2QSTRING(msg)); + + layout()->addWidget(invite->dialog_); + + addSystemMessage(ChatWindow::ChatMessage(msg), ChatWindow::DefaultDirection); +} + +void QtPlainChatView::scrollToBottom() +{ + log_->ensureCursorVisible(); + log_->verticalScrollBar()->setValue(log_->verticalScrollBar()->maximum()); +} + +void QtPlainChatView::fileTransferAccept() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (!action) { + return; + } + + FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_); + if (transferIter == fileTransfers_.end()) { + return; + } + + FileTransfer* transfer = transferIter->second; + + if (transfer->senderIsSelf_) { /* if we are the sender, kick of the transfer */ + window_->onFileTransferStart(transfer->ftId_, transfer->description_); + } else { /* ask the user where to save the file first */ + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), P2QSTRING(transfer->filename_)); + if (path.isEmpty()) { + fileTransferReject(); /* because the user did not select a desintation path */ + return; + } + window_->onFileTransferAccept(transfer->ftId_, Q2PSTRING(path)); + } + + setFileTransferStatus(transfer->ftId_, ChatWindow::Negotiating, transfer->message_); +} + +void QtPlainChatView::fileTransferReject() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (action) { + window_->onFileTransferCancel(action->id_); + fileTransferFinish(); + } +} + +void QtPlainChatView::fileTransferFinish() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (action) { + FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_); + if (transferIter != fileTransfers_.end()) { + delete transferIter->second; /* cause the dialog to close */ + fileTransfers_.erase(transferIter); + } + } +} + +void QtPlainChatView::acceptMUCInvite() +{ + AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender()); + if (action) { + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(action->jid_.toString(), action->password_, boost::optional<std::string>(), false, false, action->isImpromptu_, action->isContinuation_)); + delete action->parent_; + } +} + +void QtPlainChatView::rejectMUCInvite() +{ + AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender()); + if (action) { + /* NOTE: no action required to reject an invite? */ + delete action->parent_; + } +} + +QtPlainChatView::FileTransfer::FileTransfer(QtPlainChatView* parent, bool senderIsSelf, const std::string& ftId, const std::string& filename, const ChatWindow::FileTransferState state, const std::string &desc, const std::string& msg, bool initializing) +: PopupDialog(parent), bar_(0), senderIsSelf_(senderIsSelf), ftId_(ftId), filename_(filename), description_(desc), message_(msg), initializing_(initializing) +{ + QHBoxLayout* layout = new QHBoxLayout; + QLabel* statusLabel = new QLabel; + layout_->addWidget(statusLabel); + layout_->addWidget(new QLabel); /* padding */ + + if (initializing_) { + FileTransfer::Action* accept = new FileTransfer::Action(senderIsSelf?"Confirm":"Accept", ftId); + parent->connect(accept, SIGNAL(clicked()), SLOT(fileTransferAccept())); + layout_->addWidget(accept); + FileTransfer::Action* reject = new FileTransfer::Action(senderIsSelf?"Cancel":"Reject", ftId); + parent->connect(reject, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(reject); + statusLabel->setText(P2QSTRING(msg)); + return; + } + + std::string status = msg; + + switch (state) { + case ChatWindow::WaitingForAccept: { + status = "Waiting for user to accept <i>" + filename + "</i>..."; + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Negotiating: { + status = "Preparing to transfer <i>" + filename + "</i>..."; + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Transferring: { + status = "Transferring <i>" + filename + "</i>..."; + bar_ = new QProgressBar; + bar_->setRange(0, 100); + bar_->setValue(0); + layout->addWidget(bar_); + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Canceled: { + status = "File <i>" + filename + "</i> was canceled."; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + case ChatWindow::Finished: { + status = "File <i>" + filename + "</i> was transfered successfully."; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + case ChatWindow::FTFailed: { + status = "File transfer failed: <i>" + filename + "</i>"; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + } + + statusLabel->setText(P2QSTRING(status)); +} + +void QtPlainChatView::LogTextEdit::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu *menu = createStandardContextMenu(); + menu->exec(event->globalPos()); + delete menu; +} } |