// $Id: BasicMessageAppend.java,v 1.18 2001/10/19 12:28:32 ramsdell Exp $
// Copyright (c) 2000. The MITRE Corporation (http://www.mitre.org/).
// All rights reserved.
// The SIMP Service comes with ABSOLUTELY NO WARRANTY.
// See the SIMP Service License Agreement for details.
package org.mitre.sipchat;
import java.awt.Color;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import java.awt.event.*;
import java.util.*;
import java.text.DateFormat;
//import org.mitre.simp.util.IMA;
import org.mitre.sipchat.model.presence.Presentity;
//import org.mitre.simp.security.SimpSigner;
/**
* A text pane for displaying conversation messages. Allows the
* messages to be displayed with optional sender identification and
* time stamps. BUG: the message in the pane are never purged so that
* it is possible to run out of memory.
* @version March 2000
* @author Galen B. Williamson
* @author John D. Ramsdell (adapted from TextAppend)
*/
public class BasicMessageAppend
extends JTextPane
implements MessageAppend
{
public static final String CVS_VERSION = "$Id: BasicMessageAppend.java,v 1.18 2001/10/19 12:28:32 ramsdell Exp $";
protected final static int DEFAULT_MAX_MESSAGES = 1000;
private final StyleContext sc = new StyleContext();
private final Style normal =
sc.addStyle("normal", sc.getStyle(StyleContext.DEFAULT_STYLE));
private final LinkedList messages = new LinkedList();
private DefaultStyledDocument doc;
private boolean showDate = false;
private boolean longUser = false;
private boolean showSignatureStatus = false;
private SimpleAttributeSet me_headerStyle = new SimpleAttributeSet();
private SimpleAttributeSet you_headerStyle = new SimpleAttributeSet();
private SimpleAttributeSet signatureStatus_headerStyle = new SimpleAttributeSet(normal);
private SimpleAttributeSet truncatedMsg_style = new SimpleAttributeSet(normal);
// private IMA me = null;
private Presentity me = null;
private MessageScroller messageScroller = null;
private int maxMessages = DEFAULT_MAX_MESSAGES;
public JComponent getJComponent() {
return this;
}
/**
* Create a MessageAppend text pane.
*/
public BasicMessageAppend() {
this(null);
}
public BasicMessageAppend(int maxMessages) {
this(null, maxMessages);
}
// public BasicMessageAppend(IMA me, int maxMessages) {
public BasicMessageAppend( Presentity me, int maxMessage ) {
this(me);
this.maxMessages = maxMessages;
}
// public BasicMessageAppend(IMA me) {
public BasicMessageAppend( Presentity me ) {
this.me = me;
doc = createNewDocument();
setStyledDocument(doc);
setEditable(false);
StyleConstants.setForeground(me_headerStyle, meColor);
StyleConstants.setBold(me_headerStyle, false);
StyleConstants.setFontFamily(me_headerStyle,
StyleConstants.getFontFamily(normal));
StyleConstants.setForeground(you_headerStyle, youColor);
StyleConstants.setBold(you_headerStyle, false);
StyleConstants.setFontFamily(you_headerStyle,
StyleConstants.getFontFamily(normal));
StyleConstants.setForeground(signatureStatus_headerStyle, signedForegroundColor);
StyleConstants.setBackground(signatureStatus_headerStyle, signedBackgroundColor);
StyleConstants.setBold(signatureStatus_headerStyle, true);
// StyleConstants.setFontFamily(signatureStatus_headerStyle,
// StyleConstants.getFontFamily(normal));
StyleConstants.setFontSize(signatureStatus_headerStyle,
StyleConstants.getFontSize(normal) - 2);
StyleConstants.setForeground(truncatedMsg_style, truncatedMsgColor);
StyleConstants.setBold(truncatedMsg_style, true);
}
/**
* @return <code>false</code>
*/
public boolean isFocusTraversable() {
return false;
}
/**
* Indicate whether messages should be shown with the full
* Instant Messaging Address, or just the username, i.e.,
* "user" versus "user@host".
*/
public boolean getShowLongUser() {
return longUser;
}
/**
* Indicate whether messages should be shown with time stamps.
* Note: time stamps indicate the time the message was added,
* since messages do not carry time stamps.
*/
public boolean getShowDate() {
return showDate;
}
/**
* Indicate whether messages should be shown with signature status messages.
*/
public boolean getShowSignatureStatus() {
return showSignatureStatus;
}
/**
* Indicate whether messages should be shown with the full
* Instant Messaging Address, or just the username, i.e.,
* "user" versus "user@host".
*/
public void setShowLongUser(boolean show) {
if (longUser != show) {
longUser = show;
refresh();
}
}
/**
* Indicate whether messages should be shown with time stamps.
* Note: time stamps indicate the time the message was added,
* since messages do not carry time stamps.
*/
public void setShowDate(boolean show) {
if (showDate != show) {
showDate = show;
refresh();
}
}
/**
* Indicate whether messages should be shown with signature status messages.
*/
public void setShowSignatureStatus(boolean show) {
if (showSignatureStatus != show) {
showSignatureStatus = show;
refresh();
}
}
/**
* Set the maximum message count.
*/
public void setMaximumMessageCount(int maxMessages) {
this.maxMessages = maxMessages;
}
/**
* Get the maximum message count.
*/
public int getMaximumMessageCount() {
return maxMessages;
}
/**
* Add a message to the text pane with the indicated sender and
* signature status.
*/
// public void addMessage(IMA from, String body, int signatureStatus) {
public void addMessage( Presentity from, String body, int signatureStatus ) {
addMessage(new Message(from, body, signatureStatus));
}
/**
* Add a message to the text pane with the indicated sender.
*/
// public void addMessage(IMA from, String body) {
public void addMessage( Presentity from, String body ) {
addMessage(new Message(from, body));
}
/**
* Add a message to the text pane with no sender.
*/
public void addMessage(String body) {
addMessage(new Message(body));
}
/**
* Add a <code>Message</code> object to the text pane.
*/
public void addMessage(Message msg) {
reallyAddMessage(msg);
}
/**
* Add a message to the text pane with no sender.
*/
public void addLine(String line) {
addLines(new String[] { line });
}
/**
* Add a message to the text pane with no sender, but follow each
* element of the argument array with a newline.
*/
public void addLines(String[] lines) {
addMessage(new Message(lines));
}
/**
* Set the date format used to print messages' time stamps.
*/
public void setDateFormat(DateFormat df) {
this.df = df;
}
/**
* Get the MessageScroller for the containing component.
*/
public MessageScroller getMessageScroller() {
return messageScroller;
}
/**
* Set the MessageScroller for the containing component.
*/
public void setMessageScroller(MessageScroller scroller) {
messageScroller = scroller;
}
/**
* Get an iterator over the stored Message objects.
*/
public Iterator getMessageIterator() {
synchronized(messages) {
LinkedList m = (LinkedList) messages.clone();
return m.listIterator();
}
}
private void scrollToBottom() {
/* This doesn't always work very well: sometimes it doesn't
* quite scroll to the bottom, but one or two lines up from
* the bottom, thus rendering the last line of conversation
* unreadable. So we go back to the original code, which
* works just fine, but doesn't generalize well. Oh well.
if (messageScroller != null) {
messageScroller.scrollToBottom();
}
*/
if (getStyledDocument() == doc)
setCaretPosition(doc.getLength());
}
private DefaultStyledDocument createNewDocument() {
return new DefaultStyledDocument(sc);
}
int lastTruncMsgSize = 0;
int lastMaxMessages = -2;
private void reallyAddMessage(final Message m) {
new Thread() {
public void run() {
synchronized(messages) {
Message rm = null;
int deleteLength = 0;
int ms = messages.size();
while (maxMessages >= 0 && ms >= maxMessages && ms > 0) {
if (ms > 0) {
rm = (Message) messages.removeFirst();
deleteLength += sizeOfMessageHeader(rm) + sizeOfMessageBody(rm);
}
ms = messages.size();
}
if (rm != null || maxMessages != lastMaxMessages) {
if (getStyledDocument() == doc) {
try {
String truncMsg = null;
if (maxMessages != lastMaxMessages && rm != null) {
doc.remove(0, lastTruncMsgSize + deleteLength);
if (maxMessages >= 0) {
truncMsg = "<Old messages truncated: limit set to "+maxMessages+">\n";
}
else {
lastTruncMsgSize = 0;
}
}
else if (rm != null) {
doc.remove(lastTruncMsgSize, deleteLength);
}
if (truncMsg != null && maxMessages >= 0) { // && rm != null) {
lastTruncMsgSize = truncMsg.length();
doc.insertString(0, truncMsg, truncatedMsg_style);
}
} catch (BadLocationException ble) { /* shouldn't happen */
ble.printStackTrace();
}
}
lastMaxMessages = maxMessages;
}
messages.addLast(m);
showMessage(m);
}
}}.start();
}
private void showMessage(final Message m) {
if (SwingUtilities.isEventDispatchThread()) {
new Thread() {
public void run() {
showMessage(m);
}}.start();
}
else {
reallyShowMessage(m);
}
}
protected Style getBodyStyle() {
return normal;
}
private void reallyShowMessage(final Message m) {
synchronized(messages) {
String[] body = m.getBody();
// IMA from = m.getFrom();
Presentity from = m.getFrom();
SimpleAttributeSet style = (from != null && !from.equals(me)) ? you_headerStyle : me_headerStyle;
try {
insertHeader(doc, m, style);
} catch (BadLocationException ble1) { /* can't happen */ }
for (int i = 0; i < body.length; i++) {
if (body[i] == null)
body[i] = "";
try {
doc.insertString(doc.getLength(), body[i], getBodyStyle());
} catch (BadLocationException ble2) { /* can't happen */ }
doc.setLogicalStyle(doc.getLength() - 1, normal);
try {
doc.insertString(doc.getLength(), "\n", null);
} catch (BadLocationException ble3) { /* can't happen */ }
}
scrollToBottom();
}
}
private void refresh() {
new Thread() {
public void run () {
synchronized(messages) {
doc = createNewDocument();
if (lastTruncMsgSize > 0) {
try {
String truncMsg = getStyledDocument().getText(0, lastTruncMsgSize);
doc.insertString(0, truncMsg, truncatedMsg_style);
}
catch (BadLocationException ble) { /* shouldn't happen */
ble.printStackTrace();
}
}
Iterator i = messages.listIterator();
while (i.hasNext())
reallyShowMessage((Message)i.next());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setStyledDocument(doc);
scrollToBottom();
}});
}
}}.start();
}
private DateFormat df = DateFormat.getTimeInstance();
protected void insertHeader(Document doc, Message msg, SimpleAttributeSet style)
throws BadLocationException
{
String s = "";
String status = msg.getSignatureStatus();
// IMA from = msg.getFrom();
Presentity from = msg.getFrom();
if (from != null) {
/** SRJ check the next line */
String s1 = longUser ? from.nameAddr() : from.getFullname();
if ((getShowSignatureStatus() && status != null) || getShowDate()) {
s1 += " ";
}
doc.insertString(doc.getLength(), s1, style);
}
if (getShowSignatureStatus() && status != null) {
doc.insertString(doc.getLength(), status, signatureStatus_headerStyle);
}
if (getShowDate()) {
s += "["+df.format(msg.getDate())+"]";
}
if (from != null || getShowDate()) {
s += ": ";
doc.insertString(doc.getLength(), s, style);
}
}
protected int sizeOfMessageHeader(Message msg) {
StringBuffer sb = new StringBuffer();
String s = "";
String status = msg.getSignatureStatus();
// IMA from = msg.getFrom();
Presentity from = msg.getFrom();
if (from != null) {
/** SRJ check next line */
String s1 = longUser ? from.toString() : from.uri();
if ((getShowSignatureStatus() && status != null) || getShowDate()) {
s1 += " ";
}
sb.append(s1);
}
if (getShowSignatureStatus() && status != null) {
sb.append(status);
}
if (getShowDate()) {
s += "["+df.format(msg.getDate())+"]";
}
if (from != null || getShowDate()) {
s += ": ";
sb.append(s);
}
return sb.length();
}
protected int sizeOfMessageBody(Message msg) {
int len = 0;
int newLineLen = "\n".length();
String[] s = msg.getBody();
for (int i = 0; i < s.length; i++) {
String ss = s[i];
if (ss == null) ss = "";
len += ss.length()+newLineLen;
}
return len;
}
}