Adds last message correction functionality. Implementation provided by Ivan Vergiliev.

cusax-fix
Yana Stamcheva 14 years ago
parent 99a732d5f4
commit 2a5e9c4895

@ -215,4 +215,7 @@ service.gui.SECURITY_ON=6FC93C
service.gui.SECURITY_OFF=ED0000
# Going secure status color.
service.gui.GOING_SECURE=FFC01B
service.gui.GOING_SECURE=FFC01B
# Chat editor correction message background color.
service.gui.CHAT_EDIT_MESSAGE_BACKGROUND=fffbc3

@ -167,6 +167,7 @@ service.gui.DOWNLOAD_NOW=&Download now
service.gui.DRAG_FOR_SHARING=Drag here anything you want to share...
service.gui.DURATION=duration
service.gui.EDIT=&Edit
service.gui.EDITED_AT=edited at {0}
service.gui.EMPTY_HISTORY=&Empty history
service.gui.ENABLE_DESKTOP_REMOTE_CONTROL=Enable desktop remote control
service.gui.ENABLE_TYPING_NOTIFICATIONS=Tell others when we are writing to them (send chat activity)

@ -20,6 +20,7 @@
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.text.html.HTML.*;
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.customcontrols.*;
@ -363,6 +364,68 @@ public void setBounds(int x, int y, int width, int height)
// }
// }
/**
* Retrieves the contents of the sent message with the given ID.
*
* @param messageUID The ID of the message to retrieve.
* @return The contents of the message, or null if the message is not found.
*/
public String getMessageContents(String messageUID)
{
Element root = document.getDefaultRootElement();
Element e = document.getElement(root, Attribute.ID, messageUID);
if (e == null)
{
logger.warn("Could not find message with ID" + messageUID);
return null;
}
int elemLen = e.getEndOffset() - e.getStartOffset();
String res = null;
try
{
res = document.getText(e.getStartOffset(), elemLen);
}
catch (BadLocationException exc)
{
logger.warn("Could not get message contents for message "
+ "with ID" + messageUID, exc);
}
return res;
}
/**
* Creates a tag that shows the last edit time of a message, in the format
* (Edited at ...).
* If <tt>date < 0</tt>, returns an empty tag that serves as a placeholder
* for future corrections of this message.
*
* @param messageUID The ID of the edited message.
* @param date The date when the message was last edited, or -1 to generate
* an empty tag.
* @return The string representation of the tag.
*/
private String generateEditedAtTag(String messageUID, long date)
{
StringBuilder res = new StringBuilder();
// Use a <cite /> tag here as most of the other inline tags (e.g. h1-7,
// b, i) cause different problems when used in setOuterHTML.
res.append("<cite id='");
res.append(messageUID);
res.append("-editedAt'> ");
if (date > 0)
{
res.append("&nbsp;");
String contents = GuiActivator.getResources().getI18NString(
"service.gui.EDITED_AT",
new String[] { GuiUtils.formatTime(date) }
);
res.append(contents);
}
res.append("</cite>");
return res.toString();
}
/**
* Processes the message given by the parameters.
*
@ -384,14 +447,18 @@ public String processMessage(ChatMessage chatMessage, String keyword)
String messageType = chatMessage.getMessageType();
String messageTitle = chatMessage.getMessageTitle();
String message = chatMessage.getMessage();
String messageUID = chatMessage.getMessageUID();
String msgID = "message";
String msgHeaderID = "messageHeader";
String chatString = "";
String endHeaderTag = "";
String dateString = getDateString(date);
String idAttr = messageUID == null ? "" : " id='" + messageUID + "'";
String dateAttr = " date='" + date + "'";
String editedAtTag = generateEditedAtTag(messageUID, -1);
String startDivTag = "<DIV identifier=\"" + msgID + "\">";
String startDivTag = "<DIV identifier=\"" + msgID + "\"" + idAttr + ">";
String startHistoryDivTag
= "<DIV identifier=\"" + msgID + "\" style=\"color:#707070;\">";
String startSystemDivTag
@ -417,7 +484,7 @@ public String processMessage(ChatMessage chatMessage, String keyword)
this.lastIncomingMsgTimestamp = System.currentTimeMillis();
chatString = "<h2 identifier=\"" + msgHeaderID + "\""
+ " date=\"" + date + "\">"
+ dateAttr + ">"
+ "<a style=\"color:#ef7b1e;"
+ "font-weight:bold;"
+ "text-decoration:none;\" "
@ -427,8 +494,8 @@ public String processMessage(ChatMessage chatMessage, String keyword)
chatString
+= dateString + contactDisplayName + " at "
+ GuiUtils.formatTime(date)
+ endHeaderTag + startDivTag + startPlainTextTag
+ GuiUtils.formatTime(date) + editedAtTag + endHeaderTag
+ startDivTag + startPlainTextTag
+ formatMessage(message, contentType, keyword)
+ endPlainTextTag + endDivTag;
}
@ -451,7 +518,7 @@ else if (messageType.equals(Chat.SMS_MESSAGE))
else if (messageType.equals(Chat.OUTGOING_MESSAGE))
{
chatString = "<h3 identifier=\"" + msgHeaderID + "\""
+ " date=\"" + date + "\">"
+ dateAttr + ">"
+ "<a style=\"color:#2e538b;"
+ "font-weight:bold;"
+ "text-decoration:none;\" "
@ -461,7 +528,7 @@ else if (messageType.equals(Chat.OUTGOING_MESSAGE))
chatString
+= dateString + contactDisplayName + " at "
+ GuiUtils.formatTime(date) + endHeaderTag
+ GuiUtils.formatTime(date) + editedAtTag + endHeaderTag
+ startDivTag + startPlainTextTag
+ formatMessage(message, contentType, keyword)
+ endPlainTextTag + endDivTag;
@ -514,7 +581,7 @@ else if (messageType.equals(Chat.ERROR_MESSAGE))
else if (messageType.equals(Chat.HISTORY_INCOMING_MESSAGE))
{
chatString = "<h2 identifier=\"" + msgHeaderID + "\""
+ " date=\"" + date + "\">"
+ dateAttr + ">"
+ "<a style=\"color:#ef7b1e;"
+ "font-weight:bold;"
+ "text-decoration:none;\" "
@ -524,15 +591,15 @@ else if (messageType.equals(Chat.HISTORY_INCOMING_MESSAGE))
chatString
+= dateString + contactDisplayName
+ " at " + GuiUtils.formatTime(date)
+ endHeaderTag + startHistoryDivTag + startPlainTextTag
+ " at " + GuiUtils.formatTime(date) + endHeaderTag
+ editedAtTag + startHistoryDivTag + startPlainTextTag
+ formatMessage(message, contentType, keyword)
+ endPlainTextTag + endDivTag;
}
else if (messageType.equals(Chat.HISTORY_OUTGOING_MESSAGE))
{
chatString = "<h3 identifier=\"" + msgHeaderID + "\""
+ " date=\"" + date + "\">"
+ dateAttr + ">"
+ "<a style=\"color:#2e538b;"
+ "font-weight:bold;"
+ "text-decoration:none;\" "
@ -542,8 +609,8 @@ else if (messageType.equals(Chat.HISTORY_OUTGOING_MESSAGE))
chatString
+= dateString
+ contactDisplayName
+ " at " + GuiUtils.formatTime(date) + endHeaderTag
+ contactDisplayName + " at " + GuiUtils.formatTime(date)
+ editedAtTag + endHeaderTag
+ startHistoryDivTag + startPlainTextTag
+ formatMessage(message, contentType, keyword)
+ endPlainTextTag + endDivTag;
@ -563,6 +630,68 @@ public String processMessage(ChatMessage chatMessage)
return processMessage(chatMessage, null);
}
/**
* Replaces the contents of the message with ID of the corrected message
* specified in chatMessage, with this message.
*
* @param chatMessage A <tt>ChatMessage</tt> that contains all the required
* information to correct the old message.
*/
public void correctMessage(ChatMessage chatMessage)
{
String correctedUID = chatMessage.getCorrectedMessageUID();
Element root = document.getDefaultRootElement();
Element e = document.getElement(root, Attribute.ID, correctedUID);
if (e == null)
{
logger.warn("Could not find message with ID " + correctedUID);
return;
}
int len = e.getEndOffset() - e.getStartOffset();
StringBuilder newContents = new StringBuilder();
String bgColor = GuiActivator.getResources().getColorString(
"service.gui.CHAT_EDIT_MESSAGE_BACKGROUND");
newContents.append("<div identifier='message' id='");
newContents.append(chatMessage.getMessageUID());
newContents.append("' bgcolor='");
newContents.append(bgColor);
newContents.append("'>");
if (chatMessage.getContentType().equals(TEXT_CONTENT_TYPE))
{
newContents.append(START_PLAINTEXT_TAG);
newContents.append(chatMessage.getMessage());
newContents.append(END_PLAINTEXT_TAG);
}
else
{
newContents.append(chatMessage.getMessage());
}
newContents.append("</div>");
Element header = document.getElement(root, Attribute.ID,
correctedUID + "-editedAt");
try
{
if (header != null)
{
String newHeaderContents = generateEditedAtTag(
chatMessage.getMessageUID(), chatMessage.getDate());
document.setOuterHTML(header, newHeaderContents);
}
document.setOuterHTML(e, newContents.toString());
}
catch (BadLocationException ex)
{
logger.error("Could not replace chat message", ex);
}
catch (IOException ex)
{
logger.error("Could not replace chat message", ex);
}
}
/**
* Appends the given string at the end of the contained in this panel
* document.

@ -49,6 +49,17 @@ public class ChatMessage
* The content type of the message.
*/
private final String contentType;
/**
* A unique identifier for this message.
*/
private String messageUID;
/**
* The unique identifier of the message that this message should replace,
* or <tt>null</tt> if this is a new message.
*/
private String correctedMessageUID;
/**
* Creates a <tt>ChatMessage</tt> by specifying all parameters of the
@ -65,7 +76,8 @@ public ChatMessage( String contactName,
String message,
String contentType)
{
this(contactName, null, date, messageType, null, message, contentType);
this(contactName, null, date, messageType,
null, message, contentType, null, null);
}
/**
@ -86,7 +98,7 @@ public ChatMessage( String contactName,
String contentType)
{
this(contactName, null, date, messageType,
messageTitle, message, contentType);
messageTitle, message, contentType, null, null);
}
/**
@ -107,7 +119,7 @@ public ChatMessage( String contactName,
String contentType)
{
this(contactName, contactDisplayName, date, messageType,
null, message, contentType);
null, message, contentType, null, null);
}
/**
@ -119,6 +131,8 @@ public ChatMessage( String contactName,
* @param messageType the type (INCOMING or OUTGOING)
* @param message the content
* @param contentType the content type (e.g. "text", "text/html", etc.)
* @param messageUID The ID of the message.
* @param correctedMessageUID The ID of the message being replaced.
*/
public ChatMessage( String contactName,
String contactDisplayName,
@ -126,7 +140,9 @@ public ChatMessage( String contactName,
String messageType,
String messageTitle,
String message,
String contentType)
String contentType,
String messageUID,
String correctedMessageUID)
{
this.contactName = contactName;
this.contactDisplayName = contactDisplayName;
@ -135,6 +151,8 @@ public ChatMessage( String contactName,
this.messageTitle = messageTitle;
this.message = message;
this.contentType = contentType;
this.messageUID = messageUID;
this.correctedMessageUID = correctedMessageUID;
}
/**
@ -216,4 +234,26 @@ public String getContentType()
{
return contentType;
}
/**
* Returns the UID of this message.
*
* @return the UID of this message.
*/
public String getMessageUID()
{
return messageUID;
}
/**
* Returns the UID of the message that this message replaces, or
* <tt>null</tt> if this is a new message.
*
* @return the UID of the message that this message replaces, or
* <tt>null</tt> if this is a new message.
*/
public String getCorrectedMessageUID()
{
return correctedMessageUID;
}
}

@ -138,6 +138,17 @@ public class ChatPanel
*/
private final Hashtable<String, Object> activeFileTransfers
= new Hashtable<String, Object>();
/**
* The ID of the message being corrected, or <tt>null</tt> if
* not correcting any message.
*/
private String correctedMessageUID = null;
/**
* The ID of the last sent message in this chat.
*/
private String lastSentMessageUID = null;
/**
* Creates a <tt>ChatPanel</tt> which is added to the given chat window.
@ -489,6 +500,17 @@ public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt)
ChatConversationPanel.HTML_CONTENT_TYPE);
}
/**
* Returns the ID of the last message sent in this chat, or <tt>null</tt>
* if no messages have been sent yet.
*
* @return the ID of the last message sent in this chat, or <tt>null</tt>
* if no messages have been sent yet.
*/
public String getLastSentMessageUID() {
return lastSentMessageUID;
}
/**
* Every time the chat panel is shown we set it as a current chat panel.
* This is done here and not in the Tab selection listener, because the tab
@ -589,7 +611,8 @@ private void processHistory( Collection<Object> historyList,
evt.getTimestamp(),
messageType,
evt.getSourceMessage().getContent(),
evt.getSourceMessage().getContentType());
evt.getSourceMessage().getContentType(),
evt.getSourceMessage().getMessageUID());
}
else if(o instanceof MessageReceivedEvent)
{
@ -612,7 +635,8 @@ else if(o instanceof MessageReceivedEvent)
evt.getTimestamp(),
messageType,
evt.getSourceMessage().getContent(),
evt.getSourceMessage().getContentType());
evt.getSourceMessage().getContentType(),
evt.getSourceMessage().getMessageUID());
}
}
else if(o instanceof ChatRoomMessageDeliveredEvent)
@ -684,7 +708,8 @@ else if (o instanceof FileRecord)
public void addMessage(String contactName, long date,
String messageType, String message, String contentType)
{
addMessage(contactName, null, date, messageType, message, contentType);
addMessage(contactName, null, date, messageType, message, contentType,
null, null);
}
/**
@ -701,11 +726,12 @@ public void addMessage(String contactName, long date,
* @param contentType the content type
*/
public void addMessage(String contactName, String displayName, long date,
String messageType, String message, String contentType)
String messageType, String message, String contentType,
String messageUID, String correctedMessageUID)
{
ChatMessage chatMessage
= new ChatMessage(contactName, displayName, date,
messageType, message, contentType);
ChatMessage chatMessage = new ChatMessage(contactName, displayName,
date, messageType, null, message, contentType,
messageUID, correctedMessageUID);
this.addChatMessage(chatMessage);
@ -769,11 +795,15 @@ public void run()
}
else
{
appendChatMessage(chatMessage);
displayChatMessage(chatMessage);
}
// change the last history message timestamp after we add one.
this.lastHistoryMsgTimestamp = chatMessage.getDate();
if (chatMessage.getMessageType().equals(Chat.OUTGOING_MESSAGE))
{
this.lastSentMessageUID = chatMessage.getMessageUID();
}
}
/**
@ -809,6 +839,20 @@ public void addErrorMessage(String contactName,
message, "text");
}
private void displayChatMessage(ChatMessage chatMessage)
{
if (chatMessage.getCorrectedMessageUID() != null
&& conversationPanel.getMessageContents(
chatMessage.getCorrectedMessageUID()) != null)
{
applyMessageCorrection(chatMessage);
}
else
{
appendChatMessage(chatMessage);
}
}
/**
* Passes the message to the contained <code>ChatConversationPanel</code>
* for processing and appends it at the end of the conversationPanel
@ -853,6 +897,17 @@ private void appendChatMessage(ChatMessage chatMessage)
processedMessage, chatMessage.getContentType());
}
/**
* Passes the message to the contained <code>ChatConversationPanel</code>
* for processing and replaces the specified message with this one.
*
* @param chatMessage The message used as a correction.
*/
private void applyMessageCorrection(ChatMessage message)
{
conversationPanel.correctMessage(message);
}
/**
* Passes the message to the contained <code>ChatConversationPanel</code>
* for processing.
@ -874,10 +929,38 @@ private String processHistoryMessage(String contactName,
String messageType,
String message,
String contentType)
{
return processHistoryMessage(contactName, contactDisplayName,
date, messageType, message, contentType, null);
}
/**
* Passes the message to the contained <code>ChatConversationPanel</code>
* for processing.
*
* @param contactName The name of the contact sending the message.
* @param contactDisplayName the display name of the contact sending the
* message
* @param date The time at which the message is sent or received.
* @param messageType The type of the message. One of OUTGOING_MESSAGE
* or INCOMING_MESSAGE.
* @param message The message text.
* @param contentType the content type of the message (html or plain text)
* @param messageId The ID of the message.
*
* @return a string containing the processed message.
*/
private String processHistoryMessage(String contactName,
String contactDisplayName,
long date,
String messageType,
String message,
String contentType,
String messageId)
{
ChatMessage chatMessage = new ChatMessage(
contactName, contactDisplayName, date,
messageType, null, message, contentType);
messageType, null, message, contentType, messageId, null);
String processedMessage =
this.conversationPanel.processMessage(chatMessage);
@ -1372,7 +1455,7 @@ protected void sendInstantMessage()
OperationSetBasicInstantMessaging.HTML_MIME_TYPE).trim();
plainText = getTextFromWriteArea(
OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE);
OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE).trim();
// clear the message earlier
// to avoid as much as possible to not sending it twice (double enter)
@ -1399,8 +1482,19 @@ protected void sendInstantMessage()
try
{
chatSession.getCurrentChatTransport()
.sendInstantMessage(messageText, mimeType);
if (isMessageCorrectionActive()
&& chatSession.getCurrentChatTransport()
.allowsMessageCorrections())
{
chatSession.getCurrentChatTransport().correctInstantMessage(
messageText, mimeType, correctedMessageUID);
}
else
{
chatSession.getCurrentChatTransport().sendInstantMessage(
messageText, mimeType);
}
stopMessageCorrection();
}
catch (IllegalStateException ex)
{
@ -1458,6 +1552,58 @@ protected void sendInstantMessage()
}
}
/**
* Enters editing mode for the last sent message in this chat.
*/
public void startLastMessageCorrection()
{
startMessageCorrection(lastSentMessageUID);
}
/**
* Enters editing mode for the message with the specified id - puts the
* message contents in the write panel and changes the background.
*
* @param correctedMessageUID The ID of the message being corrected.
*/
public void startMessageCorrection(String correctedMessageUID)
{
this.correctedMessageUID = correctedMessageUID;
this.refreshWriteArea();
String messageContents
= conversationPanel.getMessageContents(correctedMessageUID);
if (messageContents == null)
{
this.stopMessageCorrection();
return;
}
Color bgColor = new Color(GuiActivator.getResources()
.getColor("service.gui.CHAT_EDIT_MESSAGE_BACKGROUND"));
this.writeMessagePanel.setEditorPaneBackground(bgColor);
this.setMessage(messageContents);
}
/**
* Exits editing mode, clears the write panel and the background.
*/
public void stopMessageCorrection()
{
this.correctedMessageUID = null;
this.writeMessagePanel.setEditorPaneBackground(Color.WHITE);
this.refreshWriteArea();
}
/**
* Returns whether a message is currently being edited.
*
* @return <tt>true</tt> if a message is currently being edited,
* <tt>false</tt> otherwise.
*/
public boolean isMessageCorrectionActive()
{
return correctedMessageUID != null;
}
/**
* Listens for SMS messages and shows them in the chat.
*/
@ -2358,7 +2504,7 @@ private void addIncomingEvents()
if (incomingEvent instanceof ChatMessage)
{
this.appendChatMessage((ChatMessage) incomingEvent);
this.displayChatMessage((ChatMessage) incomingEvent);
}
else if (incomingEvent instanceof ChatConversationComponent)
{

@ -35,6 +35,15 @@ public interface ChatTransport
* messaging, otherwise returns <code>false</code>
*/
public boolean allowsInstantMessage();
/**
* Returns <tt>true</tt> if this chat transport supports message
* corrections and false otherwise.
*
* @return <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*/
public boolean allowsMessageCorrections();
/**
* Returns <code>true</code> if this chat transport supports sms
@ -100,6 +109,20 @@ public interface ChatTransport
public void sendInstantMessage( String message,
String mimeType)
throws Exception;
/**
* Sends <tt>message</tt> as a message correction through this transport,
* specifying the mime type (html or plain text) and the id of the
* message to replace.
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @param correctedMessageUID The ID of the message being corrected by
* this message.
*/
public void correctInstantMessage(String message, String mimeType,
String correctedMessageUID);
/**
* Determines whether this chat transport supports the supplied content type

@ -674,6 +674,27 @@ else if(contacts.size() == 1)
ex.printStackTrace();
}
}
else if (e.getKeyCode() == KeyEvent.VK_UP)
{
// Only enters editing mode if the write panel is empty in
// order not to lose the current message contents, if any.
if (this.chatPanel.getLastSentMessageUID() != null
&& this.chatPanel.isWriteAreaEmpty())
{
this.chatPanel.startLastMessageCorrection();
}
}
else if (e.getKeyCode() == KeyEvent.VK_DOWN)
{
if (chatPanel.isMessageCorrectionActive())
{
Document doc = editorPane.getDocument();
if (editorPane.getCaretPosition() == doc.getLength())
{
chatPanel.stopMessageCorrection();
}
}
}
}
public void keyReleased(KeyEvent e) {}
@ -1297,4 +1318,15 @@ public void removeUpdate(DocumentEvent event)
smsNumberLabel.setText(String.valueOf(smsNumberCount));
}
}
/**
* Sets the background of the write area to the specified color.
*
* @param color The color to set the background to.
*/
public void setEditorPaneBackground(Color color)
{
this.centerPanel.setBackground(color);
this.editorPane.setBackground(color);
}
}

@ -110,9 +110,6 @@ private void checkImCaps()
}
}
/**
* Returns the contact associated with this transport.
* @return the contact associated with this transport
@ -193,6 +190,30 @@ else if (contact.getProtocolProvider()
return false;
}
/**
* Returns <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*
* @return <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*/
public boolean allowsMessageCorrections()
{
OperationSetContactCapabilities capOpSet = getProtocolProvider()
.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
{
return capOpSet.getOperationSet(
contact, OperationSetMessageCorrection.class) != null;
}
else
{
return contact.getProtocolProvider().getOperationSet(
OperationSetMessageCorrection.class) != null;
}
}
/**
* Returns <code>true</code> if this chat transport supports sms
* messaging, otherwise returns <code>false</code>.
@ -260,8 +281,8 @@ public boolean allowsFileTransfer()
}
/**
* Sends the given instant message trough this chat transport, by specifying
* the mime type (html or plain text).
* Sends the given instant message through this chat transport,
* by specifying the mime type (html or plain text).
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
@ -298,6 +319,45 @@ public void sendInstantMessage( String message,
imOpSet.sendInstantMessage(contact, msg);
}
/**
* Sends <tt>message</tt> as a message correction through this transport,
* specifying the mime type (html or plain text) and the id of the
* message to replace.
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @param correctedMessageUID The ID of the message being corrected by
* this message.
*/
public void correctInstantMessage(String message, String mimeType,
String correctedMessageUID)
{
if (!allowsMessageCorrections())
{
return;
}
OperationSetMessageCorrection mcOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetMessageCorrection.class);
Message msg;
if (mimeType.equals(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)
&& mcOpSet.isContentTypeSupported(
OperationSetBasicInstantMessaging.HTML_MIME_TYPE))
{
msg = mcOpSet.createMessage(message,
OperationSetBasicInstantMessaging.HTML_MIME_TYPE,
"utf-8", "");
}
else
{
msg = mcOpSet.createMessage(message);
}
mcOpSet.correctMessage(contact, msg, correctedMessageUID);
}
/**
* Determines whether this chat transport supports the supplied content type
*

@ -316,4 +316,31 @@ public long getMaximumFileLength()
// TODO Auto-generated method stub
return 0;
}
/**
* Returns <tt>true</tt> if this chat transport supports message
* corrections and false otherwise.
*
* @return <tt>true</tt> if this chat transport supports message
* corrections and false otherwise.
*/
public boolean allowsMessageCorrections()
{
return false;
}
/**
* Sends <tt>message</tt> as a message correction through this transport,
* specifying the mime type (html or plain text) and the id of the
* message to replace.
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @param correctedMessageUID The ID of the message being corrected by
* this message.
*/
public void correctInstantMessage(String message, String mimeType,
String correctedMessageUID)
{}
}

@ -318,4 +318,31 @@ public Object getDescriptor()
{
return chatRoom;
}
/**
* Sends <tt>message</tt> as a message correction through this transport,
* specifying the mime type (html or plain text) and the id of the
* message to replace.
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @param correctedMessageUID The ID of the message being corrected by
* this message.
*/
public void correctInstantMessage(String message, String mimeType,
String correctedMessageUID)
{}
/**
* Returns <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*
* @return <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*/
public boolean allowsMessageCorrections()
{
return false;
}
}

@ -278,8 +278,11 @@ private HTMLDocument createHistory(Collection<Object> historyRecords)
.getAccountDisplayName(protocolProvider),
evt.getTimestamp(),
Chat.OUTGOING_MESSAGE,
null,
evt.getSourceMessage().getContent(),
evt.getSourceMessage().getContentType());
evt.getSourceMessage().getContentType(),
evt.getSourceMessage().getMessageUID(),
null);
}
else if(o instanceof MessageReceivedEvent)
{
@ -290,8 +293,11 @@ else if(o instanceof MessageReceivedEvent)
evt.getSourceContact().getDisplayName(),
evt.getTimestamp(),
Chat.INCOMING_MESSAGE,
null,
evt.getSourceMessage().getContent(),
evt.getSourceMessage().getContentType());
evt.getSourceMessage().getContentType(),
evt.getSourceMessage().getMessageUID(),
null);
}
else if(o instanceof ChatRoomMessageReceivedEvent)
{

@ -238,8 +238,8 @@ public void messageReceived(MessageReceivedEvent evt)
if(metaContact != null)
{
messageReceived(protocolContact,
metaContact, message, eventType, evt.getTimestamp());
messageReceived(protocolContact, metaContact, message, eventType,
evt.getTimestamp(), evt.getCorrectedMessageUID());
}
else
{
@ -266,7 +266,8 @@ private void messageReceived(final Contact protocolContact,
final MetaContact metaContact,
final Message message,
final int eventType,
final long timestamp)
final long timestamp,
final String correctedMessageUID)
{
if(!SwingUtilities.isEventDispatchThread())
{
@ -274,8 +275,8 @@ private void messageReceived(final Contact protocolContact,
{
public void run()
{
messageReceived(protocolContact,
metaContact, message, eventType, timestamp);
messageReceived(protocolContact, metaContact, message,
eventType, timestamp, correctedMessageUID);
}
});
return;
@ -315,7 +316,9 @@ else if(eventType == MessageReceivedEvent.SMS_MESSAGE_RECEIVED)
timestamp,
messageType,
message.getContent(),
message.getContentType());
message.getContentType(),
message.getMessageUID(),
correctedMessageUID);
chatWindowManager.openChat(chatPanel, false);
@ -361,7 +364,9 @@ public void messageDelivered(MessageDeliveredEvent evt)
evt.getTimestamp(),
Chat.OUTGOING_MESSAGE,
msg.getContent(),
msg.getContentType());
msg.getContentType(),
msg.getMessageUID(),
evt.getCorrectedMessageUID());
}
}
@ -429,7 +434,9 @@ else if (evt.getErrorCode()
System.currentTimeMillis(),
Chat.OUTGOING_MESSAGE,
sourceMessage.getContent(),
sourceMessage.getContentType());
sourceMessage.getContentType(),
sourceMessage.getMessageUID(),
evt.getCorrectedMessageUID());
chatPanel.addErrorMessage(
metaContact.getDisplayName(),

@ -33,4 +33,21 @@ public MessageJabberImpl(String content, String contentType,
{
super(content, contentType, contentEncoding, subject);
}
/**
* Creates an instance of this Message with the specified parameters.
*
* @param content the text content of the message.
* @param contentType a MIME string indicating the content type of the
* <tt>content</tt> String.
* @param contentEncoding a MIME String indicating the content encoding of
* the <tt>content</tt> String.
* @param subject the subject of the message or null for empty.
* @param
*/
public MessageJabberImpl(String content, String contentType,
String contentEncoding, String subject, String messageUID)
{
super(content, contentType, contentEncoding, subject, messageUID);
}
}

@ -9,6 +9,7 @@
import java.util.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.mailnotification.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.Message;
import net.java.sip.communicator.service.protocol.event.*;
@ -34,6 +35,7 @@
*/
public class OperationSetBasicInstantMessagingJabberImpl
extends AbstractOperationSetBasicInstantMessaging
implements OperationSetMessageCorrection
{
/**
* Our class logger
@ -129,6 +131,13 @@ private class TargetAddress
this.jabberProvider = provider;
provider.addRegistrationStateChangeListener(
new RegistrationStateListener());
ProviderManager man = ProviderManager.getInstance();
MessageCorrectionExtensionProvider extProvider =
new MessageCorrectionExtensionProvider();
man.addExtensionProvider(MessageCorrectionExtension.ELEMENT_NAME,
MessageCorrectionExtension.NAMESPACE,
extProvider);
}
/**
@ -159,6 +168,13 @@ public Message createMessage(String content, String contentType,
return new MessageJabberImpl(content, contentType, encoding, subject);
}
private Message createMessage(String content, String contentType,
String messageUID)
{
return new MessageJabberImpl(content, contentType,
DEFAULT_MIME_ENCODING, null, messageUID);
}
/**
* Determines wheter the protocol provider (or the protocol itself) support
* sending and receiving offline messages. Most often this method would
@ -350,18 +366,18 @@ private void putJidForAddress(String address, String jid)
}
/**
* Sends the <tt>message</tt> to the destination indicated by the
* <tt>to</tt> contact.
*
* @param to the <tt>Contact</tt> to send <tt>message</tt> to
* @param message the <tt>Message</tt> to send.
* @throws java.lang.IllegalStateException if the underlying stack is
* not registered and initialized.
* @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an
* instance of ContactImpl.
* Helper function used to send a message to a contact, with the given
* extensions attached.
*
* @param to The contact to send the message to.
* @param message The message to send.
* @param extensions The XMPP extensions that should be attached to the
* message before sending.
* @return The MessageDeliveryEvent that resulted after attempting to
* send this message, so the calling function can modify it if needed.
*/
public void sendInstantMessage(Contact to, Message message)
throws IllegalStateException, IllegalArgumentException
private MessageDeliveredEvent sendMessage(Contact to, Message message,
PacketExtension[] extensions)
{
if( !(to instanceof ContactJabberImpl) )
throw new IllegalArgumentException(
@ -382,8 +398,14 @@ public void sendInstantMessage(Contact to, Message message)
Chat chat = obtainChatInstance(toJID);
msg.setPacketID(message.getMessageUID());
msg.setTo(toJID);
for (PacketExtension ext : extensions)
{
msg.addExtension(ext);
}
if (logger.isTraceEnabled())
logger.trace("Will send a message to:" + toJID
+ " chat.jid=" + chat.getParticipant()
@ -396,7 +418,7 @@ public void sendInstantMessage(Contact to, Message message)
= messageDeliveryPendingTransform(msgDeliveryPendingEvt);
if (msgDeliveryPendingEvt == null)
return;
return null;
String content = msgDeliveryPendingEvt
.getSourceMessage().getContent();
@ -421,7 +443,7 @@ public void sendInstantMessage(Contact to, Message message)
// this is plain text so keep it as it is.
msg.setBody(content);
}
//msg.addExtension(new Version());
MessageEventManager.
@ -434,15 +456,52 @@ public void sendInstantMessage(Contact to, Message message)
// msgDeliveredEvt = messageDeliveredTransform(msgDeliveredEvt);
if (msgDeliveredEvt != null)
fireMessageEvent(msgDeliveredEvt);
return msgDeliveredEvt;
}
catch (XMPPException ex)
{
logger.error("message not send", ex);
logger.error("message not sent", ex);
return null;
}
}
/**
* Sends the <tt>message</tt> to the destination indicated by the
* <tt>to</tt> contact.
*
* @param to the <tt>Contact</tt> to send <tt>message</tt> to
* @param message the <tt>Message</tt> to send.
* @throws java.lang.IllegalStateException if the underlying stack is
* not registered and initialized.
* @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an
* instance of ContactImpl.
*/
public void sendInstantMessage(Contact to, Message message)
throws IllegalStateException, IllegalArgumentException
{
MessageDeliveredEvent msgDelivered =
sendMessage(to, message, new PacketExtension[0]);
fireMessageEvent(msgDelivered);
}
/**
* Replaces the message with ID <tt>correctedMessageUID</tt> sent to
* the contact <tt>to</tt> with the message <tt>message</tt>
*
* @param to The contact to send the message to.
* @param message The new message.
* @param correctedMessageUID The ID of the message being replaced.
*/
public void correctMessage(Contact to, Message message,
String correctedMessageUID)
{
PacketExtension[] exts = new PacketExtension[1];
exts[0] = new MessageCorrectionExtension(correctedMessageUID);
MessageDeliveredEvent msgDelivered = sendMessage(to, message, exts);
msgDelivered.setCorrectedMessageUID(correctedMessageUID);
fireMessageEvent(msgDelivered);
}
/**
* Utility method throwing an exception if the stack is not properly
* initialized.
@ -581,7 +640,8 @@ public void processPacket(Packet packet)
+ msg.toXML());
}
Message newMessage = createMessage(msg.getBody());
Message newMessage = createMessage(msg.getBody(),
DEFAULT_MIME_TYPE, msg.getPacketID());
//check if the message is available in xhtml
PacketExtension ext = msg.getExtension(
@ -619,11 +679,20 @@ public void processPacket(Packet packet)
receivedMessage =
receivedMessage.replaceAll("&apos;", "&#39;");
newMessage =
createMessage(receivedMessage, HTML_MIME_TYPE);
newMessage = createMessage(receivedMessage,
HTML_MIME_TYPE, msg.getPacketID());
}
}
PacketExtension correctionExtension =
msg.getExtension(MessageCorrectionExtension.NAMESPACE);
String correctedMessageUID = null;
if (correctionExtension != null)
{
correctedMessageUID = ((MessageCorrectionExtension)
correctionExtension).getCorrectedMessageUID();
}
Contact sourceContact
= opSetPersPresence.findContactByID(fromUserID);
@ -652,6 +721,7 @@ public void processPacket(Packet packet)
MessageDeliveryFailedEvent ev
= new MessageDeliveryFailedEvent(newMessage,
sourceContact,
correctedMessageUID,
errorResultCode);
// ev = messageDeliveryFailedTransform(ev);
@ -681,9 +751,8 @@ public void processPacket(Packet packet)
.createVolatileContact(fromUserID);
}
MessageReceivedEvent msgReceivedEvt
= new MessageReceivedEvent(
newMessage, sourceContact , System.currentTimeMillis() );
MessageReceivedEvent msgReceivedEvt = new MessageReceivedEvent(
newMessage, sourceContact, correctedMessageUID);
// msgReceivedEvt = messageReceivedTransform(msgReceivedEvt);

@ -9,6 +9,7 @@
import java.util.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.caps.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
@ -70,6 +71,7 @@ public class OperationSetContactCapabilitiesJabberImpl
static
{
OFFLINE_OPERATION_SETS.add(OperationSetBasicInstantMessaging.class);
OFFLINE_OPERATION_SETS.add(OperationSetMessageCorrection.class);
OPERATION_SETS_TO_FEATURES.put(
OperationSetBasicTelephony.class,
@ -98,6 +100,13 @@ public class OperationSetContactCapabilitiesJabberImpl
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RTP_VIDEO
});
OPERATION_SETS_TO_FEATURES.put(
OperationSetMessageCorrection.class,
new String[]
{
MessageCorrectionExtension.NAMESPACE
});
CAPS_OPERATION_SETS_TO_FEATURES.put(
OperationSetBasicTelephony.class,
new String[]

@ -25,6 +25,7 @@
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
import net.java.sip.communicator.impl.protocol.jabber.sasl.*;
import net.java.sip.communicator.service.certificate.*;
@ -1787,6 +1788,10 @@ protected void initialize(String screenname,
if(versionManager == null)
versionManager = new VersionManager(this);
supportedFeatures.add(MessageCorrectionExtension.NAMESPACE);
addSupportedOperationSet(OperationSetMessageCorrection.class,
basicInstantMessaging);
isInitialized = true;
}
}

@ -0,0 +1,109 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection;
import org.jivesoftware.smack.packet.*;
/**
* Represents an XMPP Message correction extension, as defined in XEP-308.
*
* @author Ivan Vergiliev
*/
public class MessageCorrectionExtension
implements PacketExtension
{
/**
* The XMPP namespace that this extension belongs to.
*/
public static final String NAMESPACE = "urn:xmpp:message-correct:0";
/**
* The XMPP namespace that Swift IM use to send message corrections.
* Temporary until they start using the standard one.
*/
public static final String SWIFT_NAMESPACE =
"http://swift.im/protocol/replace";
/**
* The XML element name of this extension.
*/
public static final String ELEMENT_NAME = "replace";
/**
* Name of the attribute that specifies the ID of the message
* being corrected.
*/
public static final String ID_ATTRIBUTE_NAME = "id";
/**
* The ID of the message being corrected.
*/
private String correctedMessageUID;
/**
* Creates a new message correction extension that corrects the
* message specified by the passed ID.
*
* @param correctedMessageUID The ID of the message being corrected.
*/
public MessageCorrectionExtension(String correctedMessageUID)
{
this.correctedMessageUID = correctedMessageUID;
}
/**
* Returns the XML element name of this extension.
*
* @return The XML element name of this extension.
*/
public String getElementName()
{
return ELEMENT_NAME;
}
/**
* Returns the XML namespace this extension belongs to.
*
* @return The XML namespace this extension belongs to.
*/
public String getNamespace()
{
return NAMESPACE;
}
/**
* Construct an XML element representing this extension;
* has the form '<replace id="..." xmlns="...">'.
*
* @return An XML representation of this extension.
*/
public String toXML()
{
return "<" + ELEMENT_NAME + " id='" + correctedMessageUID
+ "' xmlns='" + NAMESPACE + "' />";
}
/**
* Returns the correctedMessageUID The UID of the message being corrected.
*
* @return the correctedMessageUID The UID of the message being corrected.
*/
public String getCorrectedMessageUID()
{
return correctedMessageUID;
}
/**
* Sets the UID of the message being corrected.
*
* @param correctedMessageUID The UID of the message being corrected.
*/
public void setCorrectedMessageUID(String correctedMessageUID)
{
this.correctedMessageUID = correctedMessageUID;
}
}

@ -0,0 +1,46 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.*;
import org.xmlpull.v1.*;
/**
* Creates Smack packet extensions by parsing <replace /> tags
* from incoming XMPP packets.
*
* @author Ivan Vergiliev
*/
public class MessageCorrectionExtensionProvider
implements PacketExtensionProvider
{
/**
* Creates a new correction extension by parsing an XML element.
*
* @param parser An XML parser.
* @return A new MesssageCorrectionExtension parsed from the XML.
* @throws Exception if an error occurs parsing the XML.
*/
public PacketExtension parseExtension(XmlPullParser parser) throws Exception
{
MessageCorrectionExtension res = new MessageCorrectionExtension(null);
do
{
if (parser.getEventType() == XmlPullParser.START_TAG)
{
res.setCorrectedMessageUID(parser.getAttributeValue(
null, MessageCorrectionExtension.ID_ATTRIBUTE_NAME));
}
}
while (parser.next() != XmlPullParser.END_TAG);
return res;
}
}

@ -68,7 +68,7 @@ protected AbstractMessage(String content, String contentType,
setEncoding(encoding);
setContent(content);
this.messageUID = messageUID;
this.messageUID = messageUID == null ? createMessageUID() : messageUID;
}
protected String createMessageUID()

@ -0,0 +1,27 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.service.protocol;
/**
* Provides functionality for correcting instant messages.
*
* @author Ivan Vergiliev
*/
public interface OperationSetMessageCorrection
extends OperationSetBasicInstantMessaging
{
/**
* Replaces the message with ID <tt>correctedMessageUID</tt> sent to
* the contact <tt>to</tt> with the message <tt>message</tt>
*
* @param to The contact to send the message to.
* @param message The new message.
* @param correctedMessageUID The ID of the message being replaced.
*/
public void correctMessage(Contact to, Message message,
String correctedMessageUID);
}

@ -33,6 +33,12 @@ public class MessageDeliveredEvent
* A timestamp indicating the exact date when the event occurred.
*/
private final long timestamp;
/**
* The ID of the message being corrected, or null if this was a new message
* and not a message correction.
*/
private String correctedMessageUID;
/**
* Constructor.
@ -44,6 +50,21 @@ public MessageDeliveredEvent(Message source, Contact to)
{
this(source, to, System.currentTimeMillis());
}
/**
* Creates a <tt>MessageDeliveredEvent</tt> representing delivery of the
* <tt>source</tt> message to the specified <tt>to</tt> contact.
*
* @param source the <tt>Message</tt> whose delivery this event represents.
* @param to the <tt>Contact</tt> that this message was sent to.
* @param correctedMessageUID The ID of the message being corrected.
*/
public MessageDeliveredEvent(Message source, Contact to,
String correctedMessageUID)
{
this(source, to, System.currentTimeMillis());
this.correctedMessageUID = correctedMessageUID;
}
/**
* Creates a <tt>MessageDeliveredEvent</tt> representing delivery of the
@ -93,4 +114,26 @@ public long getTimestamp()
return timestamp;
}
/**
* Returns the ID of the message being corrected, or null if this was a
* new message and not a message correction.
*
* @return the ID of the message being corrected, or null if this was a
* new message and not a message correction.
*/
public String getCorrectedMessageUID()
{
return correctedMessageUID;
}
/**
* Sets the ID of the message being corrected to the passed ID.
*
* @param correctedMessageUID The ID of the message being corrected.
*/
public void setCorrectedMessageUID(String correctedMessageUID)
{
this.correctedMessageUID = correctedMessageUID;
}
}

@ -74,6 +74,12 @@ public class MessageDeliveryFailedEvent
*/
private final long timestamp;
/**
* The ID of the message being corrected, or null if this was a new message
* and not a message correction.
*/
private String correctedMessageUID;
/**
* Constructor.
*
@ -87,6 +93,23 @@ public MessageDeliveryFailedEvent(Message source,
{
this(source, to, errorCode, System.currentTimeMillis(), null);
}
/**
* Constructor.
*
* @param source the message
* @param to the "to" contact
* @param correctedMessageUID The ID of the message being corrected.
* @param errorCode error code
*/
public MessageDeliveryFailedEvent(Message source,
Contact to,
String correctedMessageUID,
int errorCode)
{
this(source, to, errorCode, System.currentTimeMillis(), null);
this.correctedMessageUID = correctedMessageUID;
}
/**
* Creates a <tt>MessageDeliveryFailedEvent</tt> indicating failure of
@ -190,4 +213,14 @@ public String getReason()
{
return reasonPhrase;
}
/**
* Sets the ID of the message being corrected to the passed ID.
*
* @param correctedMessageUID The ID of the message being corrected.
*/
public String getCorrectedMessageUID()
{
return correctedMessageUID;
}
}

@ -55,6 +55,12 @@ public class MessageReceivedEvent
* The type of message event that this instance represents.
*/
private int eventType = -1;
/**
* The ID of the message being corrected, or null if this is a new message
* and not a correction.
*/
private String correctedMessageUID = null;
/**
* Creates a <tt>MessageReceivedEvent</tt> representing reception of the
@ -70,6 +76,23 @@ public MessageReceivedEvent(Message source, Contact from, long timestamp)
this(source, from, timestamp, CONVERSATION_MESSAGE_RECEIVED);
}
/**
* Creates a <tt>MessageReceivedEvent</tt> representing reception of the
* <tt>source</tt> message received from the specified <tt>from</tt>
* contact.
*
* @param source the <tt>Message</tt> whose reception this event represents.
* @param from the <tt>Contact</tt> that has sent this message.
* @param timestamp the exact date when the event ocurred.
*/
public MessageReceivedEvent(Message source, Contact from,
String correctedMessageUID)
{
this(source, from, System.currentTimeMillis(),
CONVERSATION_MESSAGE_RECEIVED);
this.correctedMessageUID = correctedMessageUID;
}
/**
* Creates a <tt>MessageReceivedEvent</tt> representing reception of the
* <tt>source</tt> message received from the specified <tt>from</tt>
@ -92,10 +115,10 @@ public MessageReceivedEvent(Message source, Contact from,
}
/**
* Returns a reference to the <tt>Contact</tt> that has send the
* Returns a reference to the <tt>Contact</tt> that has sent the
* <tt>Message</tt> whose reception this event represents.
*
* @return a reference to the <tt>Contact</tt> that has send the
* @return a reference to the <tt>Contact</tt> that has sent the
* <tt>Message</tt> whose reception this event represents.
*/
public Contact getSourceContact()
@ -135,4 +158,16 @@ public int getEventType()
{
return eventType;
}
/**
* Returns the correctedMessageUID The ID of the message being corrected,
* or null if this is a new message and not a correction.
*
* @return the correctedMessageUID The ID of the message being corrected,
* or null if this is a new message and not a correction.
*/
public String getCorrectedMessageUID()
{
return correctedMessageUID;
}
}

Loading…
Cancel
Save