summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swift/QtUI/QtPlainChatView.cpp')
-rw-r--r--Swift/QtUI/QtPlainChatView.cpp340
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;
+}
}