Commits

prokhin_alexey committed a96dfc8

Message receipts

Comments (0)

Files changed (11)

pictures/bullet-received.png

Added
New image

pictures/bullet-send.png

Added
New image
         <file>icons/48x48/lime-dark.png</file>
         <file>icons/64x64/lime-dark.png</file>
         <file>icons/128x128/lime-dark.png</file>
+        <file>pictures/bullet-send.png</file>
+        <file>pictures/bullet-received.png</file>
     </qresource>
 </RCC>

src/lime/chat/textlog/textchatlog.cpp

 	documentLayout()->registerHandler(EmoticonObjectType, this);
 	init();
 	loadHistory(unit);
+
+	if (unit->flags() & ChatUnit::SupportsMessageReceipts) {
+		lconnect(unit, SIGNAL(messageReceipt(int,Lime::ChatUnit::MessageReceipt)), this,
+			[=](int id, ChatUnit::MessageReceipt receipt)
+		{
+			int *pos = m_cache.take(id);
+			if (receipt != ChatUnit::MessageNotDelivered) {
+				if (!pos)
+					return;
+				QTextCursor cursor(this);
+				cursor.beginEditBlock();
+				cursor.setPosition(*pos);
+				cursor.deleteChar();
+				cursor.insertImage(QLatin1String("bullet-received"));
+				cursor.endEditBlock();
+
+			}
+			delete pos;
+		});
+	}
 }
 
 TextChatDocument::~TextChatDocument()
 		if (m_isLastIncoming)
 			m_lastIncomingMessage = msg.text();
 		cursor.insertText(QLatin1String("\n"));
-		bool showReceived = isIncoming;
+		bool showReceived = isIncoming || msg.unit()->flags() & ChatUnit::SupportsMessageReceipts;
 		if (msg.type() & Message::HistoryMessage)
 			showReceived = true;
 		if (!showReceived)
 void TextChatDocument::init()
 {
 	QPixmap pixmap;
-	QString stylePath = theme(QLatin1String("webkitstyle"), QLatin1String("Tory"))
-	        + QLatin1String("/Contents/Resources/");
-	pixmap.load(stylePath + QLatin1String("Images/bullet-received.png"));
-	addResource(QTextDocument::ImageResource, QUrl(QLatin1String("bullet-received")), pixmap);
-	pixmap.load(stylePath + QLatin1String("Images/bullet-send.png"));
-	addResource(QTextDocument::ImageResource, QUrl(QLatin1String("bullet-send")), pixmap);
+	pixmap.load(":/pictures/bullet-received.png");
+	addResource(QTextDocument::ImageResource, QUrl("bullet-received"), pixmap);
+	pixmap.load(":/pictures//bullet-send.png");
+	addResource(QTextDocument::ImageResource, QUrl("bullet-send"), pixmap);
 	for (int i = 0; i < m_emoticons.size(); i++)
 		m_emoticons.at(i).movie->deleteLater();
 	m_cache.clear();
 		m_emoticons.at(i).movie->setPaused(!edit);
 }
 
-bool TextChatDocument::eventFilter(QObject *obj, QEvent *ev)
-{
-// 	if (ev->type() == MessageReceiptEvent::eventType()) {
-// 		MessageReceiptEvent *msgEvent = static_cast<MessageReceiptEvent *>(ev);
-// 		int *pos = m_cache.take(msgEvent->id());
-// 		debug() << msgEvent->id() << (pos ? *pos : -1);
-// 		if (pos) {
-// 			QTextCursor cursor(this);
-// 			cursor.beginEditBlock();
-// 			cursor.setPosition(*pos);
-// 			cursor.deleteChar();
-// 			cursor.insertImage(QLatin1String("bullet-received"));
-// 			cursor.endEditBlock();
-// //			cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
-// //			QTextImageFormat format;
-// //			format.setName(QLatin1String("bullet-received"));
-// //			cursor.setCharFormat(format);
-// 			delete pos;
-// 		}
-// 		return true;
-// 	}
-	return QTextDocument::eventFilter(obj, ev);
-}
-
 AbstractChatLogState *TextChatLog::createState(ChatUnit *unit)
 {
 	return new TextChatLogState(unit);

src/lime/chat/textlog/textchatlog.h

     virtual void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc,
 	                        int posInDocument, const QTextFormat &format);
     virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format);
-
-	bool eventFilter(QObject *, QEvent *);
 public slots:
 	void ensureScrolling();
 protected slots:

src/lime/chatunit.cpp

 namespace Lime
 {
 
-Signal<void(ChatUnit* /*unit*/, QString /* newName*/, QString /*oldName*/)> ChatUnit::onNameChanged;
-Signal<void(ChatUnit* /*unit*/, Message /*message*/)> ChatUnit::onMessageReceived;
-Signal<void(ChatUnit* /*unit*/, Message /*message*/)> ChatUnit::onNewUnreadMessage;
-Signal<void(ChatUnit* /*unit*/)> ChatUnit::onUnreadMessagesCleared;
-Signal<void(ChatUnit* /*unit*/)> ChatUnit::onChatOpened;
-Signal<void(ChatUnit* /*unit*/)> ChatUnit::onChatActivated;
-Signal<void(ChatUnit* /*unit*/)> ChatUnit::onChatDeactivated;
-Signal<void(ChatUnit* /*unit*/)> ChatUnit::onChatClosed;
-Signal<void(ChatUnit* /*unit*/, ChatUnit::ChatState /*state*/)> ChatUnit::onChatStateChanged;
+Signal<void(ChatUnit* unit, QString  newName, QString oldName)> ChatUnit::onNameChanged;
+Signal<void(ChatUnit* unit, Message message)> ChatUnit::onMessageReceived;
+Signal<void(ChatUnit* unit, Message message)> ChatUnit::onNewUnreadMessage;
+Signal<void(ChatUnit* unit)> ChatUnit::onUnreadMessagesCleared;
+Signal<void(ChatUnit* unit)> ChatUnit::onChatOpened;
+Signal<void(ChatUnit* unit)> ChatUnit::onChatActivated;
+Signal<void(ChatUnit* unit)> ChatUnit::onChatDeactivated;
+Signal<void(ChatUnit* unit)> ChatUnit::onChatClosed;
+Signal<void(ChatUnit* unit, ChatUnit::ChatState state)> ChatUnit::onChatStateChanged;
+Signal<void(ChatUnit* unit, int id, ChatUnit::MessageReceipt success)> ChatUnit::onMessageReceipt;
 
 ChatUnit::ChatUnit(const QString &id, Account *account, int flags) :
 	QObject(account),
 	onChatStateChanged(this, state);
 }
 
+void ChatUnit::handleMessageReceipt(int messageId, MessageReceipt success)
+{
+	emit messageReceipt(messageId, success);
+	onMessageReceipt(this, messageId, success);
+}
+
 void ChatUnit::sendChatStateImpl(ChatState state)
 {
 }

src/lime/chatunit.h

 		SupportsHtmlMessages     = 0x00000004, // The chat unit may send and(or) receive html messages.
 		ShowIdInToolTip          = 0x00000008,
 		SupportsChatStates       = 0x00000010, // The chat unit supports notifications about typing.
+		SupportsMessageReceipts  = 0x00000020, // The chat unit may send delivery receipts.
 		ChatUnitDefaultFlags     = SupportsOutgoingMessages | SupportsIncomingMessages | ShowIdInToolTip
 	};
 	enum SendMessageError
 		StateComposing,     // User is composing a message.
 		StatePaused         // User had been composing but now has stopped.
 	};
+	enum MessageReceipt
+	{
+		MessageNotDelivered,
+		MessageDelivered,
+		MessageProbablyDelivered
+	};
 
 	ChatUnit(const QString &id, Account *account, int flags = ChatUnitDefaultFlags);
 	virtual ~ChatUnit();
 	void activateChat();
 	void clearUnreadMessages();
 public: // static signals
-	static Signal<void(ChatUnit* /*unit*/, QString /* newName*/, QString /*oldName*/)> onNameChanged;
-	static Signal<void(ChatUnit* /*unit*/, Message /*message*/)> onMessageReceived;
-	static Signal<void(ChatUnit* /*unit*/, Message /*message*/)> onNewUnreadMessage;
-	static Signal<void(ChatUnit* /*unit*/)> onUnreadMessagesCleared;
-	static Signal<void(ChatUnit* /*unit*/)> onChatOpened;
-	static Signal<void(ChatUnit* /*unit*/)> onChatActivated;
-	static Signal<void(ChatUnit* /*unit*/)> onChatDeactivated;
-	static Signal<void(ChatUnit* /*unit*/)> onChatClosed;
-	static Signal<void(ChatUnit* /*unit*/, ChatState /*state*/)> onChatStateChanged;
+	static Signal<void(ChatUnit* unit, QString  newName, QString oldName)> onNameChanged;
+	static Signal<void(ChatUnit* unit, Message message)> onMessageReceived;
+	static Signal<void(ChatUnit* unit, Message message)> onNewUnreadMessage;
+	static Signal<void(ChatUnit* unit)> onUnreadMessagesCleared;
+	static Signal<void(ChatUnit* unit)> onChatOpened;
+	static Signal<void(ChatUnit* unit)> onChatActivated;
+	static Signal<void(ChatUnit* unit)> onChatDeactivated;
+	static Signal<void(ChatUnit* unit)> onChatClosed;
+	static Signal<void(ChatUnit* unit, ChatState state)> onChatStateChanged;
+	static Signal<void(ChatUnit* unit, int messageId, MessageReceipt success)> onMessageReceipt;
 signals:
 	void nameChanged(const QString &newName, const QString &oldName);
-	void messageReceived(const Message &message);
-	void newUnreadMessage(const Message &message);
+	void messageReceived(const Lime::Message &message);
+	void newUnreadMessage(const Lime::Message &message);
 	void unreadMessagesCleared();
 	void chatOpened();
 	void chatActivated();
 	void chatDeactivated();
 	void chatClosed();
-	void chatStateChanged(ChatState state);
+	void chatStateChanged(Lime::ChatUnit::ChatState state);
+	void messageReceipt(int messageId, Lime::ChatUnit::MessageReceipt success);
 protected:
 	friend class ChatManager;
 	// Protocols must call the method whenever unit's name has changed
 	void handleIncomingMessage(const Message &message);
 	// Protocol must call the method when unit's chat state has been changed.
 	void handleChatState(ChatState state);
+	// Protocol must call the method whenever a message receipt has been received.
+	void handleMessageReceipt(int messageId, MessageReceipt success);
 protected:
 	virtual SendMessageError sendMessageImpl(const Message &message) = 0;
 	virtual void sendChatStateImpl(ChatState state);

src/lime/metacontact.cpp

 				this->updateAvatar(avatar);
 		});
 	}
-	lconnect(buddy, SIGNAL(chatStateChanged(ChatState)), this, [=](ChatState state) {
+	lconnect(buddy, SIGNAL(chatStateChanged(Lime::ChatUnit::ChatState)), this, [=](ChatState state) {
 		if (m_resources.first() == buddy) {
 			if (state == m_state)
 				return;

src/protocols/jabber/jabbercontact.cpp

 							 SupportsIncomingMessages |
 							 SupportsMultiGroups |
 							 SupportsSettingName |
-							 SupportsChatStates)
+							 SupportsChatStates |
+							 SupportsMessageReceipts)
 {
 }
 

src/protocols/jabber/jabbercontact.h

 	void handlePresence(const Jreen::Presence &presence);
 	void handleIncomingMessage(const Jreen::Message &msg);
 	void handleChatState(ChatState state) { ChatUnit::handleChatState(state); }
+	void handleMessageReceipt(int id, MessageReceipt success) { ChatUnit::handleMessageReceipt(id, success); }
 	void updateAvatar(const QString &avatar) { MetaContact::updateAvatar(avatar); }
 protected:
 	virtual SendMessageError sendMessageImpl(const Lime::Message& message);

src/protocols/jabber/jabbermessagemanager.cpp

 #include "jabbercontact.h"
 #include "jabberresource.h"
 #include "jreen/chatstate.h"
+#include "jreen/receipt.h"
 
 namespace Lime
 {
 	if (!contact || message.containsPayload<Jreen::Error>())
 		return;
 
+	if(auto *receipt = message.payload<Jreen::Receipt>().data()) {
+		if (receipt->type() == Jreen::Receipt::Received) {
+			QString id = receipt->id();
+			if(id.isEmpty())
+				id = message.id(); //for slowpoke client such as Miranda
+			contact->handleMessageReceipt(id.toUInt(), ChatUnit::MessageDelivered);
+		} else {
+			//TODO send this request only when message marked as read
+			Jreen::Message request(Jreen::Message::Chat, message.from());
+			//for slowpoke clients
+			request.setID(message.id());
+			//correct behaviour
+			request.addExtension(new Jreen::Receipt(Jreen::Receipt::Received, message.id()));
+			m_account->client()->send(request);
+		}
+	}
+
 	if (auto jstate = message.payload<Jreen::ChatState>().data()) {
 		auto state = static_cast<ChatUnit::ChatState>(jstate->state());
 		auto resource = message.from().isFull() ? contact->resource(message.from().resource(), false) : 0;
 
 void JabberMessageFilter::decorate(Jreen::Message &message)
 {
+	auto receipt = new Jreen::Receipt(Jreen::Receipt::Request);
+	message.addExtension(receipt);
 }
 
 void JabberMessageFilter::reset()