[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kopete-devel
Subject:    [kopete-devel] [PATCH] Message State Notification
From:       "Roman Jarosz" <roman.jarosz () gmail ! com>
Date:       2008-08-27 20:13:41
Message-ID: op.ugkag3xvrj95b0 () localhost
[Download RAW message or body]

Hi,

I've add message state (sending/error/sent) notification into the chat window.

Messages can have four state:
StateSending: message was sent but not yet delivered.
StateSent: message was delivered
StateError: message has not been delivered
StateUnknown: state of message isn't known
              (e.g. protocol doesn't support message acknowledgment)

Each state can have its own html code that is inserted into an element
which has id="%stateElementId%". To change message state, protocol should
call ChatSession::receivedMessageState( messageId, MessageState state ),
style can also define resend message button for error state.

I've added some status html code and icons into clear style and made
a screenshot and movie with it.

http://kedge.wz.cz/kopete/kopeteft5.png
http://kedge.wz.cz/kopete/state-notification.mpg
(btw. the sending animation is smooth in reality)

I also hooked ICQ to the new notification system and while doing this
I found out that for icq we have to keep a dictionary which maps icq
icbm cookie to Kopete messageId. So maybe we should use internal message
id in receivedMessageState? (for icq it would be the cookie)

Comments and suggestions are welcome?

Regards,
Roman

PS. The ICQ part isn't finished yet.
["messagestatenf.diff" (messagestatenf.diff)]

Index: kopete/kopete/chatwindow/kopetechatwindowstyle.cpp
===================================================================
--- kopete/kopete/chatwindow/kopetechatwindowstyle.cpp	(revision 853113)
+++ kopete/kopete/chatwindow/kopetechatwindowstyle.cpp	(working copy)
@@ -48,6 +48,11 @@
 	QString actionIncomingHtml;
 	QString actionOutgoingHtml;
 	QString fileTransferIncomingHtml;
+	QString outgoingStateSendingHtml;
+	QString outgoingStateErrorHtml;
+	QString outgoingStateSentHtml;
+	QString outgoingStateUnknownHtml;
+
 	QHash<QString, bool> compactVariants;
 };
 
@@ -160,6 +165,26 @@
 	return d->fileTransferIncomingHtml;
 }
 
+QString ChatWindowStyle::getOutgoingStateSendingHtml() const
+{
+	return d->outgoingStateSendingHtml;
+}
+
+QString ChatWindowStyle::getOutgoingStateSentHtml() const
+{
+	return d->outgoingStateSentHtml;
+}
+
+QString ChatWindowStyle::getOutgoingStateErrorHtml() const
+{
+	return d->outgoingStateErrorHtml;
+}
+
+QString ChatWindowStyle::getOutgoingStateUnknownHtml() const
+{
+	return d->outgoingStateUnknownHtml;
+}
+
 bool ChatWindowStyle::hasActionTemplate() const
 {
 	return ( !d->actionIncomingHtml.isEmpty() && !d->actionOutgoingHtml.isEmpty() );
@@ -207,6 +232,10 @@
 	QString actionIncomingFile = d->baseHref + QString("Incoming/Action.html");
 	QString actionOutgoingFile = d->baseHref + QString("Outgoing/Action.html");
 	QString fileTransferIncomingFile = d->baseHref + \
QString("Incoming/FileTransferRequest.html"); +	QString outgoingStateUnknownFile = \
d->baseHref + QString("Outgoing/StateUnknown.html"); +	QString \
outgoingStateSendingFile = d->baseHref + QString("Outgoing/StateSending.html"); \
+	QString outgoingStateSentFile = d->baseHref + QString("Outgoing/StateSent.html"); \
+	QString outgoingStateErrorFile = d->baseHref + QString("Outgoing/StateError.html"); \
  QFile fileAccess;
 	// First load header file.
@@ -342,6 +371,51 @@
 		                           .arg( i18n( "Download" ), i18n( "Cancel" ) );
 		d->fileTransferIncomingHtml.replace( QLatin1String("%message%"), message );
 	}
+
+	// Load outgoing file
+	if( QFile::exists(outgoingStateUnknownFile) )
+	{
+		fileAccess.setFileName(outgoingStateUnknownFile);
+		fileAccess.open(QIODevice::ReadOnly);
+		QTextStream headerStream(&fileAccess);
+		headerStream.setCodec(QTextCodec::codecForName("UTF-8"));
+		d->outgoingStateUnknownHtml = headerStream.readAll();
+		kDebug(14000) << "Outgoing StateUnknown HTML: " << d->outgoingStateUnknownHtml;
+		fileAccess.close();
+	}
+
+	if( QFile::exists(outgoingStateSendingFile) )
+	{
+		fileAccess.setFileName(outgoingStateSendingFile);
+		fileAccess.open(QIODevice::ReadOnly);
+		QTextStream headerStream(&fileAccess);
+		headerStream.setCodec(QTextCodec::codecForName("UTF-8"));
+		d->outgoingStateSendingHtml = headerStream.readAll();
+		kDebug(14000) << "Outgoing StateSending HTML: " << d->outgoingStateSendingHtml;
+		fileAccess.close();
+	}
+
+	if( QFile::exists(outgoingStateSentFile) )
+	{
+		fileAccess.setFileName(outgoingStateSentFile);
+		fileAccess.open(QIODevice::ReadOnly);
+		QTextStream headerStream(&fileAccess);
+		headerStream.setCodec(QTextCodec::codecForName("UTF-8"));
+		d->outgoingStateSentHtml = headerStream.readAll();
+		kDebug(14000) << "Outgoing StateSent HTML: " << d->outgoingStateSentHtml;
+		fileAccess.close();
+	}
+
+	if( QFile::exists(outgoingStateErrorFile) )
+	{
+		fileAccess.setFileName(outgoingStateErrorFile);
+		fileAccess.open(QIODevice::ReadOnly);
+		QTextStream headerStream(&fileAccess);
+		headerStream.setCodec(QTextCodec::codecForName("UTF-8"));
+		d->outgoingStateErrorHtml = headerStream.readAll();
+		kDebug(14000) << "Outgoing StateError HTML: " << d->outgoingStateErrorHtml;
+		fileAccess.close();
+	}
 }
 
 void ChatWindowStyle::reload()
Index: kopete/kopete/chatwindow/chatmessagepart.cpp
===================================================================
--- kopete/kopete/chatwindow/chatmessagepart.cpp	(revision 853113)
+++ kopete/kopete/chatwindow/chatmessagepart.cpp	(working copy)
@@ -97,28 +97,6 @@
 
 class ToolTip;
 
-
-class FileTransferEventListener: public QObject, public DOM::EventListener
-{
-public:
-	virtual void handleEvent( DOM::Event &event )
-	{
-		DOM::HTMLInputElement element = event.currentTarget();
-		if ( !element.isNull() )
-		{
-			QString idType = element.id().string().left(4);
-			unsigned int messageId = element.id().string().mid(4).toUInt();
-
-			if ( idType == QLatin1String( "ftSV" ) )
-				Kopete::TransferManager::transferManager()->saveIncomingTransfer( messageId );
-			else if ( idType == QLatin1String( "ftSA" ) )
-				Kopete::TransferManager::transferManager()->saveIncomingTransfer( messageId );
-			else if ( idType == QLatin1String( "ftCC" ) )
-				Kopete::TransferManager::transferManager()->cancelIncomingTransfer( messageId );
-		}
-	}
-};
-
 class ChatMessagePart::Private
 {
 public:
@@ -127,7 +105,7 @@
 	   copyAction(0), saveAction(0), printAction(0),
 	   closeAction(0),copyURLAction(0), currentChatStyle(0),
 	   latestDirection(Kopete::Message::Inbound), \
                latestType(Kopete::Message::TypeNormal),
-	   fileTransferEventListener(0)
+	   htmlEventListener(0)
 	{}
 
 	~Private()
@@ -161,8 +139,8 @@
 	// to enable on-the-fly style changing.
 	QList<Kopete::Message> allMessages;
 	
-	// No need to delete, FileTransferEventListener is ref counted.
-	QPointer<FileTransferEventListener> fileTransferEventListener;
+	// No need to delete, HTMLEventListener is ref counted.
+	QPointer<HTMLEventListener> htmlEventListener;
 };
 /*
 class ChatMessagePart::ToolTip : public Q3ToolTip
@@ -257,6 +235,9 @@
 	connect( d->manager, SIGNAL(displayNameChanged()), this, \
SLOT(slotUpdateHeaderDisplayName()) );  connect( d->manager, SIGNAL(photoChanged()), \
this, SLOT(slotUpdateHeaderPhoto()) );  
+	connect( d->manager, SIGNAL(messageStateChanged(uint, \
Kopete::Message::MessageState)), +	         this, SLOT(messageStateChanged(uint, \
Kopete::Message::MessageState)) ); +
 	connect ( browserExtension(), SIGNAL( openUrlRequestDelayed( const KUrl &, const \
                KParts::OpenUrlArguments &, const KParts::BrowserArguments & ) ),
 	          this, SLOT( slotOpenURLRequest( const KUrl &, const \
KParts::OpenUrlArguments &, const KParts::BrowserArguments & ) ) );  
@@ -432,6 +413,21 @@
 		variantNode.setInnerText( QString("@import url(\"%1\");").arg( \
adjustStyleVariantForChatSession( variantPath) ) );  }
 
+void ChatMessagePart::messageStateChanged( uint messageId, \
Kopete::Message::MessageState state ) +{
+	QList<Kopete::Message>::Iterator it = d->allMessages.end();
+	while ( it != d->allMessages.begin() )
+	{
+		--it;
+		if ( (*it).id() == messageId )
+		{
+			(*it).setState( state );
+			changeMessageStateElement( messageId, state );
+			break;
+		}
+	}
+}
+
 void ChatMessagePart::slotAppearanceChanged()
 {
 	readOverrides();
@@ -567,14 +563,18 @@
 		chatNode.appendChild(newMessageNode);
 	}
 
-	if ( message.type() == Kopete::Message::TypeFileTransferRequest )
+	if ( message.type() == Kopete::Message::TypeNormal ) 
 	{
+		if ( message.direction() == Kopete::Message::Outbound )
+			changeMessageStateElement( message.id(), message.state() );
+	}
+	else if ( message.type() == Kopete::Message::TypeFileTransferRequest )
+	{
 		if ( message.fileTransferDisabled() )
 			disableFileTransferButtons( message.id() );
 		else
 			addFileTransferButtonsEventListener( message.id() );
 	}
-	
 
 	// Keep the direction to see on next message
 	// if it's a consecutive message
@@ -1062,6 +1062,9 @@
 		resultHTML.replace( QLatin1String("%saveFileAsHandlerId%"), QString( "ftSA%1" \
).arg( message.id() ) );  resultHTML.replace( \
QLatin1String("%cancelRequestHandlerId%"), QString( "ftCC%1" ).arg( message.id() ) ); \
} +	
+	if ( message.type() == Kopete::Message::TypeNormal && message.direction() == \
Kopete::Message::Outbound ) +		resultHTML.replace( QLatin1String( "%stateElementId%" \
), QString( "msST%1" ).arg( message.id() ) );  
 	// Replace message at the end, maybe someone could put a Adium keyword in his \
message :P  resultHTML.replace( QLatin1String("%message%"), \
formatMessageBody(message) ); @@ -1291,6 +1294,29 @@
 #endif
 }
 
+void ChatMessagePart::resendMessage( uint messageId )
+{
+	QList<Kopete::Message>::ConstIterator it, itEnd = d->allMessages.constEnd();
+	for ( it = d->allMessages.constBegin(); it != itEnd; ++it )
+	{
+		if ( (*it).id() == messageId )
+		{
+			Kopete::Message msg( (*it).from(), (*it).to() );
+			msg.setDirection( Kopete::Message::Outbound );
+			if ( (*it).format() == Qt::RichText )
+				msg.setHtmlBody( (*it).body()->toPlainText() );
+			else
+				msg.setPlainBody( (*it).body()->toHtml() );
+			
+// 			msg.setBackgroundColor( (*it).backgroundColor() );
+			msg.setForegroundColor( (*it).foregroundColor() );
+			msg.setFont( (*it).font() );
+			d->manager->sendMessage( msg );
+			break;
+		}
+	}
+}
+
 QString ChatMessagePart::adjustStyleVariantForChatSession( const QString & \
styleVariant ) const  {
 	if ( d->manager->form() == Kopete::ChatSession::Chatroom
@@ -1320,23 +1346,14 @@
 
 void ChatMessagePart::addFileTransferButtonsEventListener( unsigned int id )
 {
-	if ( !d->fileTransferEventListener )
-		d->fileTransferEventListener = new FileTransferEventListener();
+	QString elementId = QString( "ftSV%1" ).arg( id );
+	registerClickEventListener( document().getElementById( elementId ) );
 
-	QString elementId = QString( "ftSV%1" ).arg( id );
-	DOM::HTMLElement element = document().getElementById( elementId );
-	if ( !element.isNull() )
-		element.addEventListener( "click", d->fileTransferEventListener, false );
-	
 	elementId = QString( "ftSA%1" ).arg( id );
-	element = document().getElementById( elementId );
-	if ( !element.isNull() )
-		element.addEventListener( "click", d->fileTransferEventListener, false );
-	
+	registerClickEventListener( document().getElementById( elementId ) );
+
 	elementId = QString( "ftCC%1" ).arg( id );
-	element = document().getElementById( elementId );
-	if ( !element.isNull() )
-		element.addEventListener( "click", d->fileTransferEventListener, false );
+	registerClickEventListener( document().getElementById( elementId ) );
 }
 
 void ChatMessagePart::disableFileTransferButtons( unsigned int id )
@@ -1357,6 +1374,69 @@
 		element.setDisabled( true );
 }
 
+void ChatMessagePart::changeMessageStateElement( uint id, \
Kopete::Message::MessageState state ) +{
+	QString elementId = QString( "msST%1" ).arg( id );
+	DOM::HTMLElement element = document().getElementById( elementId );
+	if ( element.isNull() )
+		return;
+	
+	QString statusHTML;
+	switch ( state )
+	{
+	case Kopete::Message::StateUnknown:
+		statusHTML = d->currentChatStyle->getOutgoingStateUnknownHtml();
+		break;
+	case Kopete::Message::StateSending:
+		statusHTML = d->currentChatStyle->getOutgoingStateSendingHtml();
+		break;
+	case Kopete::Message::StateSent:
+		statusHTML = d->currentChatStyle->getOutgoingStateSentHtml();
+		break;
+	case Kopete::Message::StateError:
+		statusHTML = d->currentChatStyle->getOutgoingStateErrorHtml();
+		break;
+	}
+
+	QString resendId = QString( "msRS%1" ).arg( id );
+	statusHTML.replace( QLatin1String( "%resendHandlerId%" ), resendId );
+	element.setInnerHTML( statusHTML );
+
+	registerClickEventListener( document().getElementById( resendId ) );
+}
+
+void ChatMessagePart::registerClickEventListener( DOM::HTMLElement element )
+{
+	if ( element.isNull() )
+		return;
+	
+	if ( !d->htmlEventListener )
+	{
+		d->htmlEventListener = new HTMLEventListener();
+		connect( d->htmlEventListener, SIGNAL(resendMessage(uint)), this, \
SLOT(resendMessage(uint)) ); +	}
+	element.addEventListener( "click", d->htmlEventListener, false );
+}
+
+void HTMLEventListener::handleEvent( DOM::Event &event )
+{
+	DOM::HTMLInputElement element = event.currentTarget();
+	if ( !element.isNull() )
+	{
+		QString idType = element.id().string().left(4);
+		unsigned int messageId = element.id().string().mid(4).toUInt();
+
+		if ( idType == QLatin1String( "ftSV" ) )
+			Kopete::TransferManager::transferManager()->saveIncomingTransfer( messageId );
+		else if ( idType == QLatin1String( "ftSA" ) )
+			Kopete::TransferManager::transferManager()->saveIncomingTransfer( messageId );
+		else if ( idType == QLatin1String( "ftCC" ) )
+			Kopete::TransferManager::transferManager()->cancelIncomingTransfer( messageId );
+		else if ( idType == QLatin1String( "msRS" ) )
+			emit resendMessage( messageId );
+	}
+}
+
 #include "chatmessagepart.moc"
 
 // vim: set noet ts=4 sts=4 sw=4:
Index: kopete/kopete/chatwindow/kopetechatwindowstyle.h
===================================================================
--- kopete/kopete/chatwindow/kopetechatwindowstyle.h	(revision 853113)
+++ kopete/kopete/chatwindow/kopetechatwindowstyle.h	(working copy)
@@ -98,6 +98,11 @@
 
 	QString getFileTransferIncomingHtml() const;
 
+	QString getOutgoingStateSendingHtml() const;
+	QString getOutgoingStateSentHtml() const;
+	QString getOutgoingStateErrorHtml() const;
+	QString getOutgoingStateUnknownHtml() const;
+
 	/**
 	 * Check if the style has the support for Kopete Action template (Kopete extension)
 	 * @return true if the style has Action template.
Index: kopete/kopete/chatwindow/chatmessagepart.h
===================================================================
--- kopete/kopete/chatwindow/chatmessagepart.h	(revision 853113)
+++ kopete/kopete/chatwindow/chatmessagepart.h	(working copy)
@@ -21,8 +21,10 @@
 
 #include <khtml_part.h>
 #include <dom/html_element.h>
+#include <dom/dom2_events.h>
 
 #include <kmenu.h>
+#include <kopetemessage.h>
 
 #include <kopete_export.h>
 
@@ -143,6 +145,8 @@
 	 */
 	void setStyleVariant( const QString &variantPath );
 
+	void messageStateChanged( uint messageId, Kopete::Message::MessageState state );
+
 signals:
 	/**
 	 * Emits before the context menu is about to show
@@ -183,6 +187,8 @@
 	 * Upda the photo in the header.
 	 */
 	void slotUpdateHeaderPhoto();
+	
+	void resendMessage( uint messageId );
 
 protected:
 	virtual void khtmlDrawContentsEvent( khtml::DrawContentsEvent * );
@@ -269,10 +275,23 @@
 
 	void disableFileTransferButtons( unsigned int id );
 
+	void changeMessageStateElement( uint id, Kopete::Message::MessageState state );
+
+	void registerClickEventListener( DOM::HTMLElement element );
+
 	class Private;
 	Private *d;
 };
 
+class HTMLEventListener: public QObject, public DOM::EventListener
+{
+	Q_OBJECT
+public:
+	virtual void handleEvent( DOM::Event &event );
+Q_SIGNALS:
+	void resendMessage( uint messageId );
+};
+
 #endif
 
 // vim: set noet ts=4 sts=4 sw=4:
Index: kopete/protocols/oscar/liboscar/oscarmessage.cpp
===================================================================
--- kopete/protocols/oscar/liboscar/oscarmessage.cpp	(revision 853113)
+++ kopete/protocols/oscar/liboscar/oscarmessage.cpp	(working copy)
@@ -24,6 +24,9 @@
 #include <QByteArray>
 #include <QSharedData>
 
+#include <krandom.h>
+#include "buffer.h"
+
 #include "oscarmessageplugin.h"
 
 namespace Oscar
@@ -47,6 +50,11 @@
 		fileSize = 0;
 		fileCount = 0;
 		plugin = 0;
+		
+		Buffer b;
+		b.addDWord( (Oscar::DWORD)KRandom::random() );
+		b.addDWord( (Oscar::DWORD)KRandom::random() );
+		icbmCookie = b.buffer();
 	}
 	MessagePrivate( const MessagePrivate &other )
 		: QSharedData( other )
Index: kopete/protocols/oscar/liboscar/tasks/messageacktask.h
===================================================================
--- kopete/protocols/oscar/liboscar/tasks/messageacktask.h	(revision 0)
+++ kopete/protocols/oscar/liboscar/tasks/messageacktask.h	(revision 0)
@@ -0,0 +1,47 @@
+/*
+    messageacktask.h  - Incoming OSCAR Messaging Acknowledgement Handler
+
+    Copyright (c) 2008      by Roman Jarosz <kedgedev@centrum.cz>
+    Kopete    (c) 2008      by the Kopete developers  <kopete-devel@kde.org>
+
+    *************************************************************************
+    *                                                                       *
+    * This program is free software; you can redistribute it and/or modify  *
+    * it under the terms of the GNU General Public License as published by  *
+    * the Free Software Foundation; either version 2 of the License, or     *
+    * (at your option) any later version.                                   *
+    *                                                                       *
+    *************************************************************************
+*/
+#ifndef MESSAGEACKTASK_H
+#define MESSAGEACKTASK_H
+
+#include "task.h"
+
+#include <QString>
+#include <QByteArray>
+
+/**
+ * Handles message acks.
+ * @author Roman Jarosz
+*/
+class MessageAckTask : public Task
+{
+	Q_OBJECT
+public:
+	MessageAckTask( Task* parent );
+
+	virtual bool forMe( const Transfer* transfer ) const;
+	virtual bool take( Transfer* transfer );
+
+signals:
+	void ackReceived( const QByteArray& icbmCookie );
+
+private:
+	QByteArray m_icbmCookie;
+
+};
+
+#endif
+
+//kate: indent-mode csands; tab-width 4;
Index: kopete/protocols/oscar/liboscar/tasks/sendmessagetask.cpp
===================================================================
--- kopete/protocols/oscar/liboscar/tasks/sendmessagetask.cpp	(revision 853113)
+++ kopete/protocols/oscar/liboscar/tasks/sendmessagetask.cpp	(working copy)
@@ -71,21 +71,7 @@
 	SNAC s = { 0x0004, snacSubfamily, 0x0000, client()->snacSequence() };
 	Buffer* b = new Buffer();
 
-	if ( snacSubfamily == 0x0006 && m_message.messageType() != Oscar::MessageType::File \
                )
-	{
-		Oscar::DWORD cookie1 = KRandom::random();
-		Oscar::DWORD cookie2 = KRandom::random();
-
-		b->addDWord( cookie1 );
-		b->addDWord( cookie2 );
-
-		m_message.setIcbmCookie( b->buffer() ); //in case we need it later
-	}
-	else
-	{ //file msgs and automated responses already have a cookie
-		b->addString( m_message.icbmCookie() );
-	}
-
+	b->addString( m_message.icbmCookie() );
 	b->addWord( m_message.channel() );
 
 	b->addByte( m_message.receiver().length() );
Index: kopete/protocols/oscar/liboscar/tasks/messageacktask.cpp
===================================================================
--- kopete/protocols/oscar/liboscar/tasks/messageacktask.cpp	(revision 0)
+++ kopete/protocols/oscar/liboscar/tasks/messageacktask.cpp	(revision 0)
@@ -0,0 +1,58 @@
+/*
+    messageacktask.cpp  - Incoming OSCAR Messaging Acknowledgement Handler
+
+    Copyright (c) 2008      by Roman Jarosz <kedgedev@centrum.cz>
+    Kopete    (c) 2008      by the Kopete developers  <kopete-devel@kde.org>
+
+    *************************************************************************
+    *                                                                       *
+    * This program is free software; you can redistribute it and/or modify  *
+    * it under the terms of the GNU General Public License as published by  *
+    * the Free Software Foundation; either version 2 of the License, or     *
+    * (at your option) any later version.                                   *
+    *                                                                       *
+    *************************************************************************
+*/
+
+#include "messageacktask.h"
+
+#include <kdebug.h>
+
+#include "transfer.h"
+#include "buffer.h"
+
+MessageAckTask::MessageAckTask( Task* parent ) : Task( parent )
+{
+}
+
+bool MessageAckTask::forMe( const Transfer* transfer ) const
+{
+	const SnacTransfer* st = dynamic_cast<const SnacTransfer*>( transfer );
+	if ( !st )
+		return false;
+
+	if ( st->snacService() == 0x0004 && st->snacSubtype() == 0x000C )
+		return true;
+	else
+		return false;
+}
+
+bool MessageAckTask::take( Transfer* transfer )
+{
+	if ( forMe( transfer ) )
+	{
+		const SnacTransfer* st = dynamic_cast<const SnacTransfer*>( transfer );
+		if ( !st )
+			return false;
+
+		Buffer* b = transfer->buffer();
+		emit ackReceived( b->getBlock( 8 ) );
+		return true;
+	}
+
+	return false;
+}
+
+
+#include "messageacktask.moc"
+//kate: indent-mode csands;
Index: kopete/protocols/oscar/liboscar/client.cpp
===================================================================
--- kopete/protocols/oscar/liboscar/client.cpp	(revision 853113)
+++ kopete/protocols/oscar/liboscar/client.cpp	(working copy)
@@ -42,6 +42,7 @@
 #include "logintask.h"
 #include "connection.h"
 #include "messagereceivertask.h"
+#include "messageacktask.h"
 #include "onlinenotifiertask.h"
 #include "oscarclientstream.h"
 #include "oscarsettings.h"
@@ -118,6 +119,7 @@
 	OnlineNotifierTask* onlineNotifier;
 	OwnUserInfoTask* ownStatusTask;
 	MessageReceiverTask* messageReceiverTask;
+	MessageAckTask* messageAckTask;
 	SSIAuthTask* ssiAuthTask;
 	ICQUserInfoRequestTask* icqInfoTask;
 	ICQTlvInfoRequestTask* icqTlvInfoTask;
@@ -180,6 +182,7 @@
 	d->onlineNotifier = 0L;
 	d->ownStatusTask = 0L;
 	d->messageReceiverTask = 0L;
+	d->messageAckTask = 0L;
 	d->ssiAuthTask = 0L;
 	d->icqInfoTask = 0L;
 	d->icqTlvInfoTask = 0L;
@@ -782,6 +785,7 @@
 	d->onlineNotifier = new OnlineNotifierTask( c->rootTask() );
 	d->ownStatusTask = new OwnUserInfoTask( c->rootTask() );
 	d->messageReceiverTask = new MessageReceiverTask( c->rootTask() );
+	d->messageAckTask = new MessageAckTask( c->rootTask() );
 	d->ssiAuthTask = new SSIAuthTask( c->rootTask() );
 	d->icqInfoTask = new ICQUserInfoRequestTask( c->rootTask() );
 	d->icqTlvInfoTask = new ICQTlvInfoRequestTask( c->rootTask() );
@@ -803,6 +807,9 @@
 	connect( d->messageReceiverTask, SIGNAL( fileMessage( int, const QString, const \
                QByteArray, Buffer ) ),
 	         this, SLOT( gotFileMessage( int, const QString, const QByteArray, Buffer ) \
) );  
+	connect( d->messageAckTask, SIGNAL(ackReceived(const QByteArray&)),
+	         this, SIGNAL(messageAckReceived(const QByteArray&)) );
+
 	connect( d->ssiAuthTask, SIGNAL( authRequested( const QString&, const QString& ) ),
 	         this, SIGNAL( authRequestReceived( const QString&, const QString& ) ) );
 	connect( d->ssiAuthTask, SIGNAL( authReplied( const QString&, const QString&, bool \
) ), @@ -1697,6 +1704,7 @@
 	delete d->onlineNotifier;
 	delete d->ownStatusTask;
 	delete d->messageReceiverTask;
+	delete d->messageAckTask;
 	delete d->ssiAuthTask;
 	delete d->icqInfoTask;
 	delete d->icqTlvInfoTask;
@@ -1708,6 +1716,7 @@
 	d->onlineNotifier = 0;
 	d->ownStatusTask = 0;
 	d->messageReceiverTask = 0;
+	d->messageAckTask = 0;
 	d->ssiAuthTask = 0;
 	d->icqInfoTask = 0;
 	d->icqTlvInfoTask = 0;
Index: kopete/protocols/oscar/liboscar/client.h
===================================================================
--- kopete/protocols/oscar/liboscar/client.h	(revision 853113)
+++ kopete/protocols/oscar/liboscar/client.h	(working copy)
@@ -480,6 +480,8 @@
 	/** we've received a message */
 	void messageReceived( const Oscar::Message& );
 
+	void messageAckReceived( const QByteArray& icbmCookie );
+
 	/** we've received an authorization request */
 	void authRequestReceived( const QString& contact, const QString& reason );
 
Index: kopete/protocols/oscar/liboscar/CMakeLists.txt
===================================================================
--- kopete/protocols/oscar/liboscar/CMakeLists.txt	(revision 853113)
+++ kopete/protocols/oscar/liboscar/CMakeLists.txt	(working copy)
@@ -64,6 +64,7 @@
     tasks/onlinenotifiertask.cpp
     tasks/ssimodifytask.cpp
     tasks/messagereceivertask.cpp
+    tasks/messageacktask.cpp
     tasks/sendmessagetask.cpp
     tasks/icqtask.cpp
     tasks/offlinemessagestask.cpp
Index: kopete/protocols/oscar/icqcontactbase.cpp
===================================================================
--- kopete/protocols/oscar/icqcontactbase.cpp	(revision 853113)
+++ kopete/protocols/oscar/icqcontactbase.cpp	(working copy)
@@ -33,6 +33,8 @@
 {
 	QObject::connect( mAccount->engine(), SIGNAL(receivedXStatusMessage(const QString&, \
                int, const QString&, const QString&)),
 	                  this, SLOT(receivedXStatusMessage(const QString&, int, const \
QString&, const QString&)) ); +	QObject::connect( mAccount->engine(), \
SIGNAL(messageAckReceived(const QByteArray&)), +	                  this, \
SLOT(messageAckReceived(const QByteArray&)) );  }
 
 ICQContactBase::~ICQContactBase()
@@ -99,11 +101,23 @@
 		message.setSender( mAccount->accountId() );
 		message.setReceiver( mName );
 		mAccount->engine()->sendMessage( message );
+		mIcbmCookieToMessageId.insert( message.icbmCookie(), msg.id() );
 	} while ( msgPosition < msgText.length() );
 
+	msg.setState( Kopete::Message::StateSending );
 	manager(Kopete::Contact::CanCreate)->appendMessage(msg);
 	manager(Kopete::Contact::CanCreate)->messageSucceeded();
 }
 
+void ICQContactBase::messageAckReceived( const QByteArray& icbmCookie )
+{
+	if ( !mIcbmCookieToMessageId.contains( icbmCookie ) )
+		return;
+
+	Kopete::ChatSession* chatSession = manager();
+	if ( chatSession )
+		chatSession->receivedMessageState( mIcbmCookieToMessageId.take( icbmCookie ), \
Kopete::Message::StateSent ); +}
+
 #include "icqcontactbase.moc"
 //kate: indent-mode csands; tab-width 4; replace-tabs off; space-indent off;
Index: kopete/protocols/oscar/icqcontactbase.h
===================================================================
--- kopete/protocols/oscar/icqcontactbase.h	(revision 853113)
+++ kopete/protocols/oscar/icqcontactbase.h	(working copy)
@@ -47,6 +47,10 @@
 private slots:
 	void receivedXStatusMessage( const QString& contact, int icon, const QString& \
description, const QString& message );  
+	void messageAckReceived( const QByteArray& icbmCookie );
+
+private:
+	QMap<QByteArray,uint> mIcbmCookieToMessageId;
 };
 
 #endif
Index: kopete/libkopete/kopetechatsession.h
===================================================================
--- kopete/libkopete/kopetechatsession.h	(revision 853113)
+++ kopete/libkopete/kopetechatsession.h	(working copy)
@@ -262,6 +262,12 @@
 	void eventNotification( const QString& notificationText);
 
 	/**
+	 * Signals that a message has changed its state.
+	 * The chat window connects to this signal to update the message in chat view.
+	 */
+	void messageStateChanged( uint messageId, Kopete::Message::MessageState state );
+
+	/**
 	 * @brief A contact within the chat session changed his photo.
 	 * Used to update the contacts photo in chat window.
 	 */
@@ -288,6 +294,13 @@
 	void receivedEventNotification(  const QString& notificationText );
 
 	/**
+	 * @brief Change state of message.
+	 * It will emit the signal messageStateChanged(). Use this slot in your protocols
+	 * and plugins to change message state.
+	 */
+	void receivedMessageState( uint messageId, Kopete::Message::MessageState state );
+	
+	/**
 	 * Show a message to the chatwindow, or append it to the queue.
 	 * This is the function protocols HAVE TO call for both incoming and outgoing \
                messages
 	 * if the message must be showed in the chatwindow
Index: kopete/libkopete/kopetechatsession.cpp
===================================================================
--- kopete/libkopete/kopetechatsession.cpp	(revision 853113)
+++ kopete/libkopete/kopetechatsession.cpp	(working copy)
@@ -425,6 +425,11 @@
 	emit eventNotification( notificationText );
 }
 
+void Kopete::ChatSession::receivedMessageState( uint messageId, \
Kopete::Message::MessageState state ) +{
+	emit messageStateChanged( messageId, state );
+}
+
 void Kopete::ChatSession::setCanBeDeleted ( bool b )
 {
 	d->mCanBeDeleted = b;
Index: kopete/libkopete/kopetemessage.cpp
===================================================================
--- kopete/libkopete/kopetemessage.cpp	(revision 853113)
+++ kopete/libkopete/kopetemessage.cpp	(working copy)
@@ -48,9 +48,9 @@
 {
 public:
 	Private() //assign next message id, it can't be changed later
-		: id(nextId++), direction(Internal), format(Qt::PlainText), type(TypeNormal), \
                importance(Normal), backgroundOverride(false),
-		  foregroundOverride(false), richTextOverride(false), isRightToLeft(false), \
                timeStamp( QDateTime::currentDateTime() ),
-		  body(new QTextDocument), escapedBodyDirty(true), fileTransfer(0)
+		: id(nextId++), direction(Internal), format(Qt::PlainText), type(TypeNormal), \
importance(Normal), state(StateUnknown), +		  backgroundOverride(false), \
foregroundOverride(false), richTextOverride(false), isRightToLeft(false), +		  \
timeStamp( QDateTime::currentDateTime() ), body(new QTextDocument), \
escapedBodyDirty(true), fileTransfer(0)  {}
 	Private (const Private &other);
 	~Private();
@@ -65,6 +65,7 @@
 	MessageType type;
 	QString requestedPlugin;
 	MessageImportance importance;
+	MessageState state;
 	bool backgroundOverride;
 	bool foregroundOverride;
 	bool richTextOverride;
@@ -111,6 +112,7 @@
 	type = other.type;
 	requestedPlugin = other.requestedPlugin;
 	importance = other.importance;
+	state = other.state;
 	backgroundOverride = other.backgroundOverride;
 	foregroundOverride = other.foregroundOverride;
 	richTextOverride = other.richTextOverride;
@@ -546,6 +548,16 @@
 	return d->importance;
 }
 
+Message::MessageState Message::state() const
+{
+	return d->state;
+}
+
+void Message::setState(MessageState state)
+{
+	d->state = state;
+}
+
 ChatSession *Message::manager() const
 {
 	return d->manager;
Index: kopete/libkopete/kopetemessage.h
===================================================================
--- kopete/libkopete/kopetemessage.h	(revision 853113)
+++ kopete/libkopete/kopetemessage.h	(working copy)
@@ -112,6 +112,14 @@
 		Highlight = 2 ///< Highlight notification, for most important messages, which \
require particular attentions.  };
 
+	enum MessageState
+	{
+		StateUnknown = 0, ///< state of message isn't known (e.g. protocol doesn't support \
message acknowledgment) +		StateSending = 1, ///< message was sent but not yet \
delivered. +		StateSent = 2, ///< message was delivered
+		StateError = 3 ///< message has not been delivered
+	};
+
 	/**
 	 * Constructs a new empty message
 	 */
@@ -294,6 +302,19 @@
 	void setImportance(MessageImportance importance);
 
 	/**
+	 * @brief Accessor method for the state
+	 * @return The message state (unknown/sending/sent/error)
+	 */
+	MessageState state() const;
+
+	/**
+	 * @brief Set the state of message.
+	 * @see MessageState
+	 * @param importance The message state to set
+	 */
+	void setState(MessageState state);
+
+	/**
 	 * @brief Sets the foreground color for the message
 	 * @see foregroundColor
 	 * @param color The color



_______________________________________________
kopete-devel mailing list
kopete-devel@kde.org
https://mail.kde.org/mailman/listinfo/kopete-devel


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic