/** * $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.sparkimpl.plugin.transcripts; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.TimerTask; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.UIManager; import javax.swing.text.html.HTMLEditorKit; import org.jdesktop.swingx.calendar.DateUtils; import org.jivesoftware.MainWindowListener; import org.jivesoftware.resource.Res; import org.jivesoftware.resource.SparkRes; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.spark.SparkManager; import org.jivesoftware.spark.component.BackgroundPanel; import org.jivesoftware.spark.plugin.ContextMenuListener; import org.jivesoftware.spark.ui.ChatRoom; import org.jivesoftware.spark.ui.ChatRoomButton; import org.jivesoftware.spark.ui.ChatRoomClosingListener; import org.jivesoftware.spark.ui.ChatRoomListener; import org.jivesoftware.spark.ui.ContactItem; import org.jivesoftware.spark.ui.ContactList; import org.jivesoftware.spark.ui.VCardPanel; import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; import org.jivesoftware.spark.util.GraphicUtils; import org.jivesoftware.spark.util.SwingWorker; import org.jivesoftware.spark.util.TaskEngine; import org.jivesoftware.spark.util.UIComponentRegistry; import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; import org.jivesoftware.sparkimpl.settings.local.SettingsManager; /** * The <code>ChatTranscriptPlugin</code> is responsible for transcript handling within Spark. * * @author Derek DeMoro */ public class ChatTranscriptPlugin implements ChatRoomListener { private final String timeFormat = "HH:mm:ss"; private final String dateFormat = ((SimpleDateFormat)SimpleDateFormat.getDateInstance(SimpleDateFormat.FULL)).toPattern(); private final SimpleDateFormat notificationDateFormatter; private final SimpleDateFormat messageDateFormatter; private HashMap<ChatRoom,Message> lastMessage = new HashMap<ChatRoom,Message>(); private JDialog Frame; /** * Register the listeners for transcript persistence. */ public ChatTranscriptPlugin() { SparkManager.getChatManager().addChatRoomListener(this); notificationDateFormatter = new SimpleDateFormat(dateFormat); messageDateFormatter = new SimpleDateFormat(timeFormat); final ContactList contactList = SparkManager.getWorkspace().getContactList(); final Action viewHistoryAction = new AbstractAction() { private static final long serialVersionUID = -6498776252446416099L; public void actionPerformed(ActionEvent actionEvent) { ContactItem item = contactList.getSelectedUsers().iterator().next(); final String jid = item.getJID(); showHistory(jid); } }; viewHistoryAction.putValue(Action.NAME, Res.getString("menuitem.view.contact.history")); viewHistoryAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.HISTORY_16x16)); final Action showStatusMessageAction = new AbstractAction() { private static final long serialVersionUID = -5000370836304286019L; public void actionPerformed(ActionEvent actionEvent) { ContactItem item = contactList.getSelectedUsers().iterator().next(); showStatusMessage(item); } }; showStatusMessageAction.putValue(Action.NAME, Res.getString("menuitem.show.contact.statusmessage")); contactList.addContextMenuListener(new ContextMenuListener() { public void poppingUp(Object object, JPopupMenu popup) { if (object instanceof ContactItem) { popup.add(viewHistoryAction); popup.add(showStatusMessageAction); } } public void poppingDown(JPopupMenu popup) { } public boolean handleDefaultAction(MouseEvent e) { return false; } }); SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { public void shutdown() { persistConversations(); } public void mainWindowActivated() { } public void mainWindowDeactivated() { } }); SparkManager.getConnection().addConnectionListener(new ConnectionListener() { public void connectionClosed() { } public void connectionClosedOnError(Exception e) { persistConversations(); } public void reconnectingIn(int i) { } public void reconnectionSuccessful() { } public void reconnectionFailed(Exception exception) { } }); } public void persistConversations() { for (ChatRoom room : SparkManager.getChatManager().getChatContainer().getChatRooms()) { if (room instanceof ChatRoomImpl) { ChatRoomImpl roomImpl = (ChatRoomImpl)room; if (roomImpl.isActive()) { persistChatRoom(roomImpl); } } } } public boolean canShutDown() { return true; } public void chatRoomOpened(final ChatRoom room) { LocalPreferences pref = SettingsManager.getLocalPreferences(); if (!pref.isChatHistoryEnabled()) { return; } final String jid = room.getRoomname(); File transcriptFile = ChatTranscripts.getTranscriptFile(jid); if (!transcriptFile.exists()) { return; } if (room instanceof ChatRoomImpl) { new ChatRoomDecorator(room); } } public void chatRoomLeft(ChatRoom room) { } public void chatRoomClosed(final ChatRoom room) { // Persist only agent to agent chat rooms. if (room.getChatType() == Message.Type.chat) { persistChatRoom(room); } } public void persistChatRoom(final ChatRoom room) { LocalPreferences pref = SettingsManager.getLocalPreferences(); if (!pref.isChatHistoryEnabled()) { return; } final String jid = room.getRoomname(); final List<Message> transcripts = room.getTranscripts(); ChatTranscript transcript = new ChatTranscript(); int count = 0; int i = 0; if (lastMessage.get(room) != null) { count = transcripts.indexOf(lastMessage.get(room)) + 1; } for (Message message : transcripts) { if (i < count) { i++; continue; } lastMessage.put(room,message); HistoryMessage history = new HistoryMessage(); history.setTo(message.getTo()); history.setFrom(message.getFrom()); history.setBody(message.getBody()); Date date = (Date)message.getProperty("date"); if (date != null) { history.setDate(date); } else { history.setDate(new Date()); } transcript.addHistoryMessage(history); } ChatTranscripts.appendToTranscript(jid, transcript); } public void chatRoomActivated(ChatRoom room) { } public void userHasJoined(ChatRoom room, String userid) { } public void userHasLeft(ChatRoom room, String userid) { } public void uninstall() { // Do nothing. } private void showHistory(final String jid) { SwingWorker transcriptLoader = new SwingWorker() { public Object construct() { String bareJID = StringUtils.parseBareAddress(jid); return ChatTranscripts.getChatTranscript(bareJID); } public void finished() { final JPanel mainPanel = new BackgroundPanel(); mainPanel.setLayout(new BorderLayout()); // add search text input final JPanel topPanel = new BackgroundPanel(); topPanel.setLayout(new GridBagLayout()); final VCardPanel vacardPanel = new VCardPanel(jid); final JTextField searchField = new JTextField(25); searchField.setText(Res.getString("message.search.for.history")); searchField.setToolTipText(Res.getString("message.search.for.history")); searchField.setForeground((Color) UIManager.get("TextField.lightforeground")); topPanel.add(vacardPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(1, 5, 1, 1), 0, 0)); topPanel.add(searchField, new GridBagConstraints(1, 0, GridBagConstraints.REMAINDER, 1, 1.0, 1.0, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(1, 1, 6, 1), 0, 0)); mainPanel.add(topPanel, BorderLayout.NORTH); final JEditorPane window = new JEditorPane(); window.setEditorKit(new HTMLEditorKit()); window.setBackground(Color.white); final JScrollPane pane = new JScrollPane(window); pane.getVerticalScrollBar().setBlockIncrement(200); pane.getVerticalScrollBar().setUnitIncrement(20); mainPanel.add(pane, BorderLayout.CENTER); final JFrame frame = new JFrame(Res.getString("title.history.for", jid)); frame.setIconImage(SparkRes.getImageIcon(SparkRes.HISTORY_16x16).getImage()); frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(mainPanel, BorderLayout.CENTER); frame.pack(); frame.setSize(600, 400); window.setCaretPosition(0); window.requestFocus(); GraphicUtils.centerWindowOnScreen(frame); frame.setVisible(true); window.setEditable(false); final StringBuilder builder = new StringBuilder(); builder.append("<html><body><table cellpadding=0 cellspacing=0>"); final TimerTask transcriptTask = new TimerTask() { public void run() { ChatTranscript transcript = (ChatTranscript)get(); // reduce the size of our transcript to the last 5000Messages // This will prevent JavaOutOfHeap Errors ArrayList<HistoryMessage> toobig = (ArrayList<HistoryMessage>) transcript.getMessage(null); // Get the Maximum size from settingsfile int maxsize = SettingsManager.getLocalPreferences().getMaximumHistory(); if (toobig.size() > maxsize) { transcript = new ChatTranscript(); for(int i = toobig.size()-1; i>=toobig.size()-maxsize;--i) { transcript.addHistoryMessage(toobig.get(i)); } } final List<HistoryMessage> list = transcript.getMessage( Res.getString("message.search.for.history").equals(searchField.getText()) ? null : searchField.getText()); final String personalNickname = SparkManager.getUserManager().getNickname(); Date lastPost = null; String lastPerson = null; boolean initialized = false; for (HistoryMessage message : list) { String color = "blue"; String from = message.getFrom(); String nickname = SparkManager.getUserManager().getUserNicknameFromJID(message.getFrom()); String body = org.jivesoftware.spark.util.StringUtils.escapeHTMLTags(message.getBody()); if (nickname.equals(message.getFrom())) { String otherJID = StringUtils.parseBareAddress(message.getFrom()); String myJID = SparkManager.getSessionManager().getBareAddress(); if (otherJID.equals(myJID)) { nickname = personalNickname; } else { nickname = StringUtils.parseName(nickname); } } if (!StringUtils.parseBareAddress(from).equals(SparkManager.getSessionManager().getBareAddress())) { color = "red"; } long lastPostTime = lastPost != null ? lastPost.getTime() : 0; int diff = 0; if (DateUtils.getDaysDiff(lastPostTime, message .getDate().getTime()) != 0) { diff = DateUtils.getDaysDiff(lastPostTime, message.getDate().getTime()); } else { diff = DateUtils.getDayOfWeek(lastPostTime) - DateUtils.getDayOfWeek(message .getDate().getTime()); } if (diff != 0) { if (initialized) { builder.append("<tr><td><br></td></tr>"); } builder.append("<tr><td colspan=2><font size=4 color=gray><b><u>").append(notificationDateFormatter.format(message.getDate())).append("</u></b></font></td></tr>"); lastPerson = null; initialized = true; } String value = "[" + messageDateFormatter.format(message.getDate()) + "]   "; boolean newInsertions = lastPerson == null || !lastPerson.equals(nickname); if (newInsertions) { builder.append("<tr valign=top><td colspan=2 nowrap>"); builder.append("<font size=4 color='").append(color).append("'><b>"); builder.append(nickname); builder.append("</b></font>"); builder.append("</td></tr>"); } builder.append("<tr valign=top><td align=left nowrap>"); builder.append(value); builder.append("</td><td align=left>"); builder.append(body); builder.append("</td></tr>"); lastPost = message.getDate(); lastPerson = nickname; } builder.append("</table></body></html>"); // Handle no history if (transcript.getMessages().size() == 0) { builder.append("<b>").append(Res.getString("message.no.history.found")).append("</b>"); } window.setText(builder.toString()); builder.replace(0, builder.length(), ""); } }; searchField.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { if(e.getKeyChar() == KeyEvent.VK_ENTER) { TaskEngine.getInstance().schedule(transcriptTask, 10); searchField.requestFocus(); } } @Override public void keyPressed(KeyEvent e) { } }); searchField.addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { searchField.setText(""); searchField.setForeground((Color) UIManager.get("TextField.foreground")); } public void focusLost(FocusEvent e) { searchField.setForeground((Color) UIManager.get("TextField.lightforeground")); searchField.setText(Res.getString("message.search.for.history")); } }); TaskEngine.getInstance().schedule(transcriptTask, 10); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { window.setText(""); } @Override public void windowClosed(WindowEvent e) { frame.removeWindowListener(this); frame.dispose(); transcriptTask.cancel(); topPanel.remove(vacardPanel); } }); } }; transcriptLoader.start(); } private void showStatusMessage(ContactItem item) { Frame = new JDialog(); Frame.setTitle(item.getDisplayName() + " - Status"); JPanel pane = new JPanel(); JTextArea textArea = new JTextArea(5, 30); JButton btn_close = new JButton(Res.getString("button.close")); btn_close.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Frame.setVisible(false); } }); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); pane.add(new JScrollPane(textArea)); Frame.setLayout(new BorderLayout()); Frame.add(pane, BorderLayout.CENTER); Frame.add(btn_close, BorderLayout.SOUTH); textArea.setEditable(false); textArea.setText(item.getStatus()); Frame.setLocationRelativeTo(SparkManager.getMainWindow()); Frame.setBounds(Frame.getX() - 175, Frame.getY() - 75, 350, 150); Frame.setSize(350, 150); Frame.setResizable(false); Frame.setVisible(true); } /** * Sort HistoryMessages by date. */ final Comparator<HistoryMessage> dateComparator = new Comparator<HistoryMessage>() { public int compare(HistoryMessage messageOne, HistoryMessage messageTwo) { long time1 = messageOne.getDate().getTime(); long time2 = messageTwo.getDate().getTime(); if (time1 < time2) { return 1; } else if (time1 > time2) { return -1; } return 0; } }; private class ChatRoomDecorator implements ActionListener, ChatRoomClosingListener { private ChatRoom chatRoom; private ChatRoomButton chatHistoryButton; private final LocalPreferences localPreferences; public ChatRoomDecorator(ChatRoom chatRoom) { this.chatRoom = chatRoom; chatRoom.addClosingListener(this); // Add History Button localPreferences = SettingsManager.getLocalPreferences(); if (!localPreferences.isChatHistoryEnabled()) { return; } chatHistoryButton = UIComponentRegistry.getButtonFactory().createChatTranscriptButton(); chatRoom.addChatRoomButton(chatHistoryButton); chatHistoryButton.setToolTipText(Res.getString("tooltip.view.history")); chatHistoryButton.addActionListener(this); } public void closing() { if (localPreferences.isChatHistoryEnabled()) { chatHistoryButton.removeActionListener(this); } chatRoom.removeClosingListener(this); chatRoom = null; chatHistoryButton = null; } public void actionPerformed(ActionEvent e) { ChatRoomImpl roomImpl = (ChatRoomImpl)chatRoom; showHistory(roomImpl.getParticipantJID()); } } }