/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.spark.ui; import org.jdesktop.swingx.calendar.DateUtils; import org.jivesoftware.Spark; import org.jivesoftware.resource.SparkRes; import org.jivesoftware.resource.Res; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.DelayInformation; import org.jivesoftware.spark.ChatManager; import org.jivesoftware.spark.SparkManager; import org.jivesoftware.spark.plugin.ContextMenuListener; import org.jivesoftware.spark.util.ModelUtil; import org.jivesoftware.spark.util.log.Log; import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; import org.jivesoftware.sparkimpl.settings.local.SettingsManager; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.List; /** * The <CODE>TranscriptWindow</CODE> class. Provides a default implementation * of a Chat Window. In general, extensions could override this class * to offer more support within the chat, but should not be necessary. */ public class TranscriptWindow extends ChatArea implements ContextMenuListener { private static final long serialVersionUID = -2168845249388070573L; private final SimpleDateFormat notificationDateFormatter; private final String notificationDateFormat = ((SimpleDateFormat)SimpleDateFormat.getDateInstance(SimpleDateFormat.FULL)).toPattern(); private Date lastUpdated; /** * The default font used in the chat window for all messages. */ private Font defaultFont; private Date lastPost; /** * Creates a default instance of <code>TranscriptWindow</code>. */ public TranscriptWindow() { setEditable(false); // Set Default Font final LocalPreferences pref = SettingsManager.getLocalPreferences(); int fontSize = pref.getChatRoomFontSize(); defaultFont = new Font("Dialog", Font.PLAIN, fontSize); addMouseListener(this); addMouseMotionListener(this); setDragEnabled(true); addContextMenuListener(this); // Make sure ctrl-c works getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("Ctrl c"), "copy"); getActionMap().put("copy", new AbstractAction("copy") { private static final long serialVersionUID = 1797491846835591379L; public void actionPerformed(ActionEvent evt) { StringSelection stringSelection = new StringSelection(getSelectedText()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); } }); notificationDateFormatter = new SimpleDateFormat(notificationDateFormat); } /** * Inserts a component into the transcript window. * * @param component the component to insert. */ public void addComponent(Component component) { final StyledDocument doc = (StyledDocument)getDocument(); // The image must first be wrapped in a style Style style = doc.addStyle("StyleName", null); StyleConstants.setComponent(style, component); // Insert the image at the end of the text try { doc.insertString(doc.getLength(), "ignored text", style); doc.insertString(doc.getLength(), "\n", null); } catch (BadLocationException e) { Log.error(e); } } /** * Create and insert a message from the current user. * * @param nickname the nickname of the current user. * @param message the message to insert. * @param foreground the color to use for the message foreground. */ public void insertMessage(String nickname, Message message, Color foreground) { insertMessage(nickname, message, foreground, Color.white); } /** * Create and insert a message from the current user. * * @param nickname the nickname of the current user. * @param message the message to insert. * @param foreground the color to use for the message foreground. * @param background the color to use for the message background. */ public void insertMessage(String nickname, Message message, Color foreground, Color background) { // Check interceptors. for (TranscriptWindowInterceptor interceptor : SparkManager.getChatManager().getTranscriptWindowInterceptors()) { boolean handled = interceptor.isMessageIntercepted(this, nickname, message); if (handled) { // Do nothing. return; } } String body = message.getBody(); try { DelayInformation inf = (DelayInformation)message.getExtension("x", "jabber:x:delay"); Date sentDate; if (inf != null) { sentDate = inf.getStamp(); body = "(Offline) " + body; } else { sentDate = new Date(); } String date = getDate(sentDate); // Agent color is always blue StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, foreground); StyleConstants.setBackground(styles, background); final Document doc = getDocument(); styles.removeAttribute("link"); StyleConstants.setFontSize(styles, defaultFont.getSize()); doc.insertString(doc.getLength(), date + nickname + ": ", styles); // Reset Styles for message StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, getMessageColor()); setText(body); insertText("\n"); } catch (BadLocationException e) { Log.error("Error message.", e); } } /** * Inserts a full line using a prefix and message. * * @param prefix the prefix to use. If null is used, then only the message will be inserted. * @param message the message to insert. * @param foreground the foreground color for the message. */ public void insertPrefixAndMessage(String prefix, String message, Color foreground) { try { // Agent color is always blue StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, foreground); final Document doc = getDocument(); styles.removeAttribute("link"); StyleConstants.setFontSize(styles, defaultFont.getSize()); if (prefix != null) { doc.insertString(doc.getLength(), prefix + ": ", styles); } // Reset Styles for message StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, getMessageColor()); setText(message); insertText("\n"); } catch (BadLocationException e) { Log.error("Error message.", e); } } protected Color getMessageColor() { return Color.BLACK; } /** * Create and insert a notification message. A notification message generally is a * presence update, but can be used for most anything related to the room. * * @param message the information message to insert. * @param foregroundColor the foreground color to use. */ public synchronized void insertNotificationMessage(String message, Color foregroundColor) { try { // Agent color is always blue StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, foregroundColor); final Document doc = getDocument(); styles.removeAttribute("link"); StyleConstants.setFontSize(styles, defaultFont.getSize()); doc.insertString(doc.getLength(), "", styles); // Reset Styles for message StyleConstants.setBackground(styles, new Color(0,0,0,0)); StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, foregroundColor); setText(message); insertText("\n"); // Default back to black StyleConstants.setForeground(styles, Color.black); } catch (BadLocationException ex) { Log.error("Error message.", ex); } } /** * Create and insert a notification message. A notification message generally is a * presence update, but can be used for most anything related to the room. * * @param text the text to insert. * @param bold true to use bold text. * @param underline true to have text underlined. * @param foreground the foreground color. */ public synchronized void insertCustomText(String text, boolean bold, boolean underline, Color foreground) { try { // Agent color is always blue StyleConstants.setBold(styles, true); StyleConstants.setForeground(styles, foreground); final Document doc = getDocument(); styles.removeAttribute("link"); StyleConstants.setFontSize(styles, defaultFont.getSize()); doc.insertString(doc.getLength(), "", styles); // Reset Styles for message StyleConstants.setBold(styles, bold); StyleConstants.setUnderline(styles, underline); StyleConstants.setForeground(styles, foreground); setText(text); insertText("\n"); StyleConstants.setUnderline(styles, false); StyleConstants.setForeground(styles, Color.black); } catch (BadLocationException ex) { Log.error("Error message.", ex); } } /** * Returns the formatted date. * * @param insertDate the date to format. * @return the formatted date. */ private String getDate(Date insertDate) { final LocalPreferences pref = SettingsManager.getLocalPreferences(); if (insertDate == null) { insertDate = new Date(); } StyleConstants.setFontFamily(styles, defaultFont.getFontName()); StyleConstants.setFontSize(styles, defaultFont.getSize()); if (pref.isTimeDisplayedInChat()) { final SimpleDateFormat formatter = new SimpleDateFormat(pref.getTimeFormat()); final String date = formatter.format(insertDate); return "(" + date + ") "; } lastUpdated = insertDate; return ""; } /** * Return the last time the <code>TranscriptWindow</code> was updated. * * @return the last time the <code>TranscriptWindow</code> was updated. */ public Date getLastUpdated() { return lastUpdated; } /** * Inserts a history message. * * @param userid the userid of the sender. * @param message the message to insert. * @param date the Date object created when the message was delivered. */ public void insertHistoryMessage(String userid, String message, Date date) { try { String value; long lastPostTime = lastPost != null ? lastPost.getTime() : 0; long lastPostStartOfDay = DateUtils.startOfDayInMillis(lastPostTime); long newPostStartOfDay = DateUtils.startOfDayInMillis(date.getTime()); int diff = DateUtils.getDaysDiff(lastPostStartOfDay, newPostStartOfDay); if (diff != 0) { insertCustomText(notificationDateFormatter.format(date), true, true, Color.BLACK); } value = getDate(date); value = value + userid + ": "; lastPost = date; // Agent color is always blue StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, Color.BLACK); final Document doc = getDocument(); styles.removeAttribute("link"); StyleConstants.setFontSize(styles, defaultFont.getSize()); doc.insertString(doc.getLength(), value, styles); // Reset Styles for message StyleConstants.setBold(styles, false); StyleConstants.setForeground(styles, (Color)UIManager.get("History.foreground")); setText(message); StyleConstants.setForeground(styles, Color.BLACK); insertText("\n"); } catch (BadLocationException ex) { Log.error("Error message.", ex); } } /** * Disable the entire <code>TranscriptWindow</code> and visually represent * it as disabled. */ public void showWindowDisabled() { final Document document = getDocument(); final SimpleAttributeSet attrs = new SimpleAttributeSet(); StyleConstants.setForeground(attrs, Color.LIGHT_GRAY); final int length = document.getLength(); StyledDocument styledDocument = getStyledDocument(); styledDocument.setCharacterAttributes(0, length, attrs, false); } /** * Persist a current transcript. * * @param fileName the name of the file to save the transcript as. Note: This can be modified by the user. * @param transcript the collection of transcript. * @param headerData the string to prepend to the transcript. * @see ChatRoom#getTranscripts() */ public void saveTranscript(String fileName, List<Message> transcript, String headerData) { final LocalPreferences pref = SettingsManager.getLocalPreferences(); try { SimpleDateFormat formatter; File defaultSaveFile = new File(Spark.getSparkUserHome() + "/" + fileName); final JFileChooser fileChooser = new JFileChooser(defaultSaveFile); fileChooser.setSelectedFile(defaultSaveFile); // Show save dialog; this method does not return until the dialog is closed int result = fileChooser.showSaveDialog(this); final File selFile = fileChooser.getSelectedFile(); if (selFile != null && result == JFileChooser.APPROVE_OPTION) { final StringBuffer buf = new StringBuffer(); final Iterator<Message> transcripts = transcript.iterator(); buf.append("<html><body>"); if (headerData != null) { buf.append(headerData); } buf.append("<table width=600>"); while (transcripts.hasNext()) { final Message message = transcripts.next(); String from = message.getFrom(); if (from == null) { from = pref.getNickname(); } if (Message.Type.groupchat == message.getType()) { if (ModelUtil.hasLength(StringUtils.parseResource(from))) { from = StringUtils.parseResource(from); } } final String body = message.getBody(); final Date insertionDate = (Date)message.getProperty("insertionDate"); formatter = new SimpleDateFormat("hh:mm:ss"); String value = ""; if (insertionDate != null) { value = "(" + formatter.format(insertionDate) + ") "; } buf.append("<tr><td nowrap><font size=2>").append(value).append("<strong>").append(from).append(":</strong> ").append(body).append("</font></td></tr>"); } buf.append("</table></body></html>"); final BufferedWriter writer = new BufferedWriter(new FileWriter(selFile)); writer.write(buf.toString()); writer.close(); JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Chat transcript has been saved.", "Chat Transcript Saved", JOptionPane.INFORMATION_MESSAGE); } } catch (Exception ex) { Log.error("Unable to save chat transcript.", ex); JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Could not save transcript.", "Error", JOptionPane.ERROR_MESSAGE); } } public void cleanup() { super.releaseResources(); clear(); removeMouseListener(this); removeMouseMotionListener(this); removeContextMenuListener(this); getActionMap().remove("copy"); } public void setFont(Font font) { this.defaultFont = font; } public Font getFont() { return defaultFont; } /** * Adds Print and Clear actions. * * @param object the TransferWindow * @param popup the popup menu to add to. */ public void poppingUp(final Object object, JPopupMenu popup) { Action printAction = new AbstractAction() { private static final long serialVersionUID = -244227593637660347L; public void actionPerformed(ActionEvent actionEvent) { SparkManager.printChatTranscript((TranscriptWindow)object); } }; Action clearAction = new AbstractAction() { private static final long serialVersionUID = -5664307353522844588L; public void actionPerformed(ActionEvent actionEvent) { String user = null; try { ChatManager manager = SparkManager.getChatManager(); ChatRoom room = manager.getChatContainer().getActiveChatRoom(); user = room.getRoomname(); } catch (ChatRoomNotFoundException e) { e.printStackTrace(); } int ok = JOptionPane.showConfirmDialog((TranscriptWindow)object, Res.getString("delete.permanently"), Res.getString("delete.log.permanently"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ok == JOptionPane.YES_OPTION) { if(user != null){ // This actions must be move into Transcript Plugin! File transcriptDir = new File(SparkManager.getUserDirectory(), "transcripts"); File transcriptFile = new File(transcriptDir ,user + ".xml"); transcriptFile.delete(); transcriptFile = new File(transcriptDir,user + "_current.xml"); transcriptFile.delete(); clear(); } } } }; printAction.putValue(Action.NAME, Res.getString("action.print")); printAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.PRINTER_IMAGE_16x16)); clearAction.putValue(Action.NAME, Res.getString("action.clear")); clearAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.ERASER_IMAGE)); popup.addSeparator(); popup.add(printAction); popup.add(clearAction); } public void poppingDown(JPopupMenu popup) { } public boolean handleDefaultAction(MouseEvent e) { return false; } protected SimpleDateFormat getNotificationDateFormatter() { return notificationDateFormatter; } protected Date getLastPost() { return lastPost; } protected void setLastPost(Date lastPost) { this.lastPost = lastPost; } protected void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } }