Java tutorial
/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.impl.gui.main.chat; import java.awt.*; import java.awt.Container; import java.awt.event.*; import java.beans.*; import java.io.*; import java.util.*; import java.util.List; import javax.swing.*; import javax.swing.event.*; import javax.swing.plaf.basic.*; import javax.swing.text.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.conference.*; import net.java.sip.communicator.impl.gui.main.chat.filetransfer.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.SwingWorker; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.filehistory.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.event.*; import net.java.sip.communicator.service.metahistory.*; import net.java.sip.communicator.service.muc.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; import net.java.sip.communicator.util.skin.*; import org.apache.commons.lang3.*; import org.jitsi.util.*; /** * The <tt>ChatPanel</tt> is the panel, where users can write and send messages, * view received messages. A ChatPanel is created for a contact or for a group * of contacts in case of a chat conference. There is always one default contact * for the chat, which is the first contact which was added to the chat. * When chat is in mode "open all messages in new window", each ChatPanel * corresponds to a ChatWindow. When chat is in mode "group all messages in * one chat window", each ChatPanel corresponds to a tab in the ChatWindow. * * @author Yana Stamcheva * @author Lyubomir Marinov * @author Adam Netocny * @author Hristo Terezov */ @SuppressWarnings("serial") public class ChatPanel extends TransparentPanel implements ChatSessionRenderer, ChatSessionChangeListener, Chat, ChatConversationContainer, ChatRoomMemberRoleListener, ChatRoomLocalUserRoleListener, ChatRoomMemberPropertyChangeListener, FileTransferStatusListener, Skinnable { /** * The <tt>Logger</tt> used by the <tt>CallPanel</tt> class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(ChatPanel.class); private final JSplitPane messagePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); private JSplitPane topSplitPane; private final JPanel topPanel = new JPanel(new BorderLayout()); private final ChatConversationPanel conversationPanel; /** * Will contain the typing panel on south and centered the * conversation panel. */ private final JPanel conversationPanelContainer = new JPanel(new BorderLayout()); private final ChatWritePanel writeMessagePanel; private ChatRoomMemberListPanel chatContactListPanel; private TransparentPanel conferencePanel = new TransparentPanel(new BorderLayout()); private final ChatContainer chatContainer; private ChatRoomSubjectPanel subjectPanel; public int unreadMessageNumber = 0; /** * The label showing current typing notification. */ private JLabel typingNotificationLabel; /** * The typing notification icon. */ private final Icon typingIcon = GuiActivator.getResources().getImage("service.gui.icons.TYPING"); /** * Indicates that a typing notification event is successfully sent. */ public static final int TYPING_NOTIFICATION_SUCCESSFULLY_SENT = 1; /** * Indicates that sending a typing notification event has failed. */ public static final int TYPING_NOTIFICATION_SEND_FAILED = 0; /** * The number of messages shown per page. */ protected static final int MESSAGES_PER_PAGE = 20; private boolean isShown = false; private ChatSession chatSession; private Date firstHistoryMsgTimestamp = new Date(0); private Date lastHistoryMsgTimestamp = new Date(0); private final List<ChatFocusListener> focusListeners = new Vector<ChatFocusListener>(); private final List<ChatHistoryListener> historyListeners = new Vector<ChatHistoryListener>(); private final Vector<Object> incomingEventBuffer = new Vector<Object>(); private boolean isHistoryLoaded; /** * Stores all active file transfer requests and effective transfers with * the identifier of the transfer. */ 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; /** * Indicates whether the chat is private messaging chat or not. */ private boolean isPrivateMessagingChat = false; /** * Dialog used to join or create chat conference call. */ protected ChatConferenceCallDialog chatConferencesDialog = null; /** * Whether to use all numbers when sending sms, or just the mobiles. */ private static final String USE_ADDITIONAL_NUMBERS_PROP = "service.gui.IS_SEND_SMS_USING_ADDITIONAL_NUMBERS"; /** * Creates a <tt>ChatPanel</tt> which is added to the given chat window. * * @param chatContainer The parent window of this chat panel. */ public ChatPanel(ChatContainer chatContainer) { super(new BorderLayout()); this.chatContainer = chatContainer; this.conversationPanel = new ChatConversationPanel(this); this.conversationPanel.setPreferredSize(new Dimension(400, 200)); this.conversationPanel.getChatTextPane().setTransferHandler(new ChatTransferHandler(this)); this.conversationPanelContainer.add(conversationPanel, BorderLayout.CENTER); this.conversationPanelContainer.setBackground(Color.WHITE); initTypingNotificationLabel(conversationPanelContainer); topPanel.setBackground(Color.WHITE); topPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY)); this.writeMessagePanel = new ChatWritePanel(this); this.messagePane.setBorder(null); this.messagePane.setOpaque(false); this.messagePane.addPropertyChangeListener(new DividerLocationListener()); this.messagePane.setDividerSize(3); this.messagePane.setResizeWeight(1.0D); this.messagePane.setBottomComponent(writeMessagePanel); this.messagePane.setTopComponent(topPanel); this.add(messagePane, BorderLayout.CENTER); if (OSUtils.IS_MAC) { setOpaque(true); setBackground(new Color(GuiActivator.getResources().getColor("service.gui.MAC_PANEL_BACKGROUND"))); } this.addComponentListener(new TabSelectionComponentListener()); } /** * Sets the chat session to associate to this chat panel. * @param chatSession the chat session to associate to this chat panel */ public void setChatSession(ChatSession chatSession) { if (this.chatSession != null) { // remove old listener this.chatSession.removeChatTransportChangeListener(this); } this.chatSession = chatSession; this.chatSession.addChatTransportChangeListener(this); if ((this.chatSession != null) && this.chatSession.isContactListSupported()) { topPanel.remove(conversationPanelContainer); TransparentPanel rightPanel = new TransparentPanel(new BorderLayout()); Dimension chatConferencesListsPanelSize = new Dimension(150, 25); Dimension chatContactsListsPanelSize = new Dimension(150, 175); Dimension rightPanelSize = new Dimension(150, 200); rightPanel.setMinimumSize(rightPanelSize); rightPanel.setPreferredSize(rightPanelSize); TransparentPanel contactsPanel = new TransparentPanel(new BorderLayout()); contactsPanel.setMinimumSize(chatContactsListsPanelSize); contactsPanel.setPreferredSize(chatContactsListsPanelSize); conferencePanel.setMinimumSize(chatConferencesListsPanelSize); conferencePanel.setPreferredSize(chatConferencesListsPanelSize); this.chatContactListPanel = new ChatRoomMemberListPanel(this); this.chatContactListPanel.setOpaque(false); topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); topSplitPane.setBorder(null); // remove default borders topSplitPane.setOneTouchExpandable(true); topSplitPane.setOpaque(false); topSplitPane.setResizeWeight(1.0D); Color msgNameBackground = Color.decode(ChatHtmlUtils.MSG_NAME_BACKGROUND); // add border to the divider if (topSplitPane.getUI() instanceof BasicSplitPaneUI) { ((BasicSplitPaneUI) topSplitPane.getUI()).getDivider() .setBorder(BorderFactory.createLineBorder(msgNameBackground)); } ChatTransport chatTransport = chatSession.getCurrentChatTransport(); JPanel localUserLabelPanel = new JPanel(new BorderLayout()); JLabel localUserLabel = new JLabel(chatTransport.getProtocolProvider().getAccountID().getDisplayName()); localUserLabel.setFont(localUserLabel.getFont().deriveFont(Font.BOLD)); localUserLabel.setHorizontalAlignment(SwingConstants.CENTER); localUserLabel.setBorder(BorderFactory.createEmptyBorder(2, 0, 3, 0)); localUserLabel.setForeground(Color.decode(ChatHtmlUtils.MSG_IN_NAME_FOREGROUND)); localUserLabelPanel.add(localUserLabel, BorderLayout.CENTER); localUserLabelPanel.setBackground(msgNameBackground); JButton joinConference = new JButton( GuiActivator.getResources().getI18NString("service.gui.JOIN_VIDEO")); joinConference.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { showChatConferenceDialog(); } }); contactsPanel.add(localUserLabelPanel, BorderLayout.NORTH); contactsPanel.add(chatContactListPanel, BorderLayout.CENTER); conferencePanel.add(joinConference, BorderLayout.CENTER); rightPanel.add(conferencePanel, BorderLayout.NORTH); rightPanel.add(contactsPanel, BorderLayout.CENTER); topSplitPane.setLeftComponent(conversationPanelContainer); topSplitPane.setRightComponent(rightPanel); topPanel.add(topSplitPane); } else { if (topSplitPane != null) { if (chatContactListPanel != null) { topSplitPane.remove(chatContactListPanel); chatContactListPanel = null; } this.messagePane.remove(topSplitPane); topSplitPane = null; } topPanel.add(conversationPanelContainer); } if (chatSession instanceof MetaContactChatSession) { // The subject panel is added here, because it's specific for the // multi user chat and is not contained in the single chat chat panel. if (subjectPanel != null) { this.remove(subjectPanel); subjectPanel = null; this.revalidate(); this.repaint(); } writeMessagePanel.initPluginComponents(); writeMessagePanel.setTransportSelectorBoxVisible(true); //Enables to change the protocol provider by simply pressing the // CTRL-P key combination ActionMap amap = this.getActionMap(); amap.put("ChangeProtocol", new ChangeTransportAction()); InputMap imap = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK), "ChangeProtocol"); } else if (chatSession instanceof ConferenceChatSession) { ConferenceChatSession confSession = (ConferenceChatSession) chatSession; writeMessagePanel.setTransportSelectorBoxVisible(false); confSession.addLocalUserRoleListener(this); confSession.addMemberRoleListener(this); ChatRoom room = ((ChatRoomWrapper) chatSession.getDescriptor()).getChatRoom(); room.addMemberPropertyChangeListener(this); setConferencesPanelVisible(room.getCachedConferenceDescriptionSize() > 0); subjectPanel = new ChatRoomSubjectPanel((ConferenceChatSession) chatSession); // The subject panel is added here, because it's specific for the // multi user chat and is not contained in the single chat chat panel. this.add(subjectPanel, BorderLayout.NORTH); this.revalidate(); this.repaint(); } if (chatContactListPanel != null) { // Initialize chat participants' panel. Iterator<ChatContact<?>> chatParticipants = chatSession.getParticipants(); while (chatParticipants.hasNext()) chatContactListPanel.addContact(chatParticipants.next()); } } public void showChatConferenceDialog() { if (chatConferencesDialog == null) { chatConferencesDialog = new ChatConferenceCallDialog(ChatPanel.this); chatConferencesDialog.initConferences(); } chatConferencesDialog.setVisible(true); chatConferencesDialog.toFront(); chatConferencesDialog.pack(); } /** * Returns the chat session associated with this chat panel. * @return the chat session associated with this chat panel */ public ChatSession getChatSession() { return chatSession; } /** * Runs clean-up for associated resources which need explicit disposal (e.g. * listeners keeping this instance alive because they were added to the * model which operationally outlives this instance). */ public void dispose() { writeMessagePanel.dispose(); chatSession.dispose(); conversationPanel.dispose(); if (chatSession instanceof ConferenceChatSession) { if (chatConferencesDialog != null) chatConferencesDialog.dispose(); ConferenceChatSession confSession = (ConferenceChatSession) chatSession; confSession.removeLocalUserRoleListener(this); confSession.removeMemberRoleListener(this); ((ChatRoomWrapper) chatSession.getDescriptor()).getChatRoom().removeMemberPropertyChangeListener(this); } if (subjectPanel != null) subjectPanel.dispose(); if (this.chatContactListPanel != null) this.chatContactListPanel.dispose(); } /** * Returns the chat window, where this chat panel is added. * * @return the chat window, where this chat panel is added */ public ChatContainer getChatContainer() { return chatContainer; } /** * Returns the chat window, where this chat panel * is located. Implements the * <tt>ChatConversationContainer.getConversationContainerWindow()</tt> * method. * * @return ChatWindow The chat window, where this * chat panel is located. */ public Window getConversationContainerWindow() { return chatContainer.getFrame(); } /** * Adds a typing notification message to the conversation panel. * * @param typingNotification the typing notification to show */ public void addTypingNotification(final String typingNotification) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { addTypingNotification(typingNotification); } }); return; } typingNotificationLabel.setText(typingNotification); if (typingNotification != null && !typingNotification.equals(" ")) typingNotificationLabel.setIcon(typingIcon); else typingNotificationLabel.setIcon(null); revalidate(); repaint(); } /** * Adds a typing notification message to the conversation panel, * saying that typin notifications has not been delivered. * * @param typingNotification the typing notification to show */ public void addErrorSendingTypingNotification(final String typingNotification) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { addErrorSendingTypingNotification(typingNotification); } }); return; } typingNotificationLabel.setText(typingNotification); if (typingNotification != null && !typingNotification.equals(" ")) typingNotificationLabel.setIcon(typingIcon); else typingNotificationLabel.setIcon(null); revalidate(); repaint(); } /** * Removes the typing notification message from the conversation panel. */ public void removeTypingNotification() { addTypingNotification(" "); } /** * Initializes the typing notification label. * @param typingLabelParent the parent container * of typing notification label. */ private void initTypingNotificationLabel(JPanel typingLabelParent) { typingNotificationLabel = new JLabel(" ", SwingConstants.CENTER); typingNotificationLabel.setPreferredSize(new Dimension(500, 20)); typingNotificationLabel.setForeground(Color.GRAY); typingNotificationLabel.setFont(typingNotificationLabel.getFont().deriveFont(11f)); typingNotificationLabel.setVerticalTextPosition(JLabel.BOTTOM); typingNotificationLabel.setHorizontalTextPosition(JLabel.LEFT); typingNotificationLabel.setIconTextGap(0); typingLabelParent.add(typingNotificationLabel, BorderLayout.SOUTH); } /** * Returns the conversation panel, contained in this chat panel. * * @return the conversation panel, contained in this chat panel */ public ChatConversationPanel getChatConversationPanel() { return this.conversationPanel; } /** * Returns the write area panel, contained in this chat panel. * * @return the write area panel, contained in this chat panel */ public ChatWritePanel getChatWritePanel() { return this.writeMessagePanel; } /** * Returns the corresponding role description to the given role index. * * @param role to role index to analyse * @return String the corresponding role description */ public String getRoleDescription(ChatRoomMemberRole role) { String roleDescription = null; switch (role) { case OWNER: roleDescription = GuiActivator.getResources().getI18NString("service.gui.OWNER"); break; case ADMINISTRATOR: roleDescription = GuiActivator.getResources().getI18NString("service.gui.ADMINISTRATOR"); break; case MODERATOR: roleDescription = GuiActivator.getResources().getI18NString("service.gui.MODERATOR"); break; case MEMBER: roleDescription = GuiActivator.getResources().getI18NString("service.gui.MEMBER"); break; case GUEST: roleDescription = GuiActivator.getResources().getI18NString("service.gui.GUEST"); break; case SILENT_MEMBER: roleDescription = GuiActivator.getResources().getI18NString("service.gui.SILENT_MEMBER"); break; case OUTCAST: roleDescription = GuiActivator.getResources().getI18NString("service.gui.BANNED"); break; default: ; } return roleDescription; } /** * Implements the <tt>memberRoleChanged()</tt> method. * * @param evt */ public void memberRoleChanged(ChatRoomMemberRoleChangeEvent evt) { this.conversationPanel .appendMessageToEnd( "<DIV identifier=\"message\" style=\"color:#707070;\">" + GuiActivator.getResources().getI18NString("service.gui.IS_NOW", new String[] { evt.getSourceMember().getName(), getRoleDescription(evt.getNewRole()) }) + "</DIV>", ChatHtmlUtils.HTML_CONTENT_TYPE); } /** * Implements the <tt>localUserRoleChanged()</tt> method. * * @param evt */ public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt) { this.conversationPanel.appendMessageToEnd( "<DIV identifier=\"message\" style=\"color:#707070;\">" + GuiActivator.getResources().getI18NString( "service.gui.ARE_NOW", new String[] { getRoleDescription(evt.getNewRole()) }) + "</DIV>", ChatHtmlUtils.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; } /** * Called when the current {@link ChatTransport} has * changed. We will change current icon * * @param chatSession the {@link ChatSession} it's current * {@link ChatTransport} has changed */ @Override public void currentChatTransportChanged(ChatSession chatSession) { setChatIcon(new ImageIcon(Constants.getStatusIcon(this.chatSession.getCurrentChatTransport().getStatus()))); this.writeMessagePanel.currentChatTransportChanged(chatSession); } /** * When a property of the chatTransport has changed. */ @Override public void currentChatTransportUpdated(int eventID) { if (eventID == ChatSessionChangeListener.ICON_UPDATED) { setChatIcon( new ImageIcon(Constants.getStatusIcon(this.chatSession.getCurrentChatTransport().getStatus()))); } this.writeMessagePanel.currentChatTransportUpdated(eventID); } /** * 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 * change event is not fired when the user clicks on the close tab button * for example. */ private class TabSelectionComponentListener extends ComponentAdapter { @Override public void componentShown(ComponentEvent evt) { Component component = evt.getComponent(); Container parent = component.getParent(); if (!(parent instanceof JTabbedPane)) return; JTabbedPane tabbedPane = (JTabbedPane) parent; if (tabbedPane.getSelectedComponent() != component) return; chatContainer.setCurrentChat(ChatPanel.this); } } /** * Requests the focus in the write message area. */ public void requestFocusInWriteArea() { getChatWritePanel().getEditorPane().requestFocus(); } /** * Checks if the editor contains text. * * @return TRUE if editor contains text, FALSE otherwise. */ public boolean isWriteAreaEmpty() { JEditorPane editorPane = getChatWritePanel().getEditorPane(); Document doc = editorPane.getDocument(); try { String text = doc.getText(0, doc.getLength()); if (text == null || text.equals("")) return true; } catch (BadLocationException e) { logger.error("Failed to obtain document text.", e); } return false; } /** * Returns <tt>true</tt> if the chat is private messaging chat and * <tt>false</tt> if not. * @return <tt>true</tt> if the chat is private messaging chat and * <tt>false</tt> if not. */ public boolean isPrivateMessagingChat() { return isPrivateMessagingChat; } /** * Process history messages. * * @param historyList The collection of messages coming from history. * @param escapedMessageID The incoming message needed to be ignored if * contained in history. */ private void processHistory(Collection<Object> historyList, String escapedMessageID) { Iterator<Object> iterator = historyList.iterator(); String messageType; while (iterator.hasNext()) { Object o = iterator.next(); String historyString = ""; if (o instanceof MessageDeliveredEvent) { MessageDeliveredEvent evt = (MessageDeliveredEvent) o; ProtocolProviderService protocolProvider = evt.getDestinationContact().getProtocolProvider(); if (isGreyHistoryStyleDisabled(protocolProvider)) messageType = Chat.OUTGOING_MESSAGE; else messageType = Chat.HISTORY_OUTGOING_MESSAGE; historyString = processHistoryMessage( GuiActivator.getUIService().getMainFrame().getAccountAddress(protocolProvider), GuiActivator.getUIService().getMainFrame().getAccountDisplayName(protocolProvider), evt.getTimestamp(), messageType, evt.getSourceMessage().getContent(), evt.getSourceMessage().getContentType(), evt.getSourceMessage().getMessageUID()); } else if (o instanceof MessageReceivedEvent) { MessageReceivedEvent evt = (MessageReceivedEvent) o; ProtocolProviderService protocolProvider = evt.getSourceContact().getProtocolProvider(); if (!evt.getSourceMessage().getMessageUID().equals(escapedMessageID)) { if (isGreyHistoryStyleDisabled(protocolProvider)) messageType = Chat.INCOMING_MESSAGE; else messageType = Chat.HISTORY_INCOMING_MESSAGE; historyString = processHistoryMessage(evt.getSourceContact().getAddress(), evt.getSourceContact().getDisplayName(), evt.getTimestamp(), messageType, evt.getSourceMessage().getContent(), evt.getSourceMessage().getContentType(), evt.getSourceMessage().getMessageUID()); } } else if (o instanceof ChatRoomMessageDeliveredEvent) { ChatRoomMessageDeliveredEvent evt = (ChatRoomMessageDeliveredEvent) o; ProtocolProviderService protocolProvider = evt.getSourceChatRoom().getParentProvider(); historyString = processHistoryMessage( GuiActivator.getUIService().getMainFrame().getAccountAddress(protocolProvider), GuiActivator.getUIService().getMainFrame().getAccountDisplayName(protocolProvider), evt.getTimestamp(), Chat.HISTORY_OUTGOING_MESSAGE, evt.getMessage().getContent(), evt.getMessage().getContentType(), evt.getMessage().getMessageUID()); } else if (o instanceof ChatRoomMessageReceivedEvent) { ChatRoomMessageReceivedEvent evt = (ChatRoomMessageReceivedEvent) o; if (!evt.getMessage().getMessageUID().equals(escapedMessageID)) { historyString = processHistoryMessage(evt.getSourceChatRoomMember().getContactAddress(), evt.getSourceChatRoomMember().getName(), evt.getTimestamp(), Chat.HISTORY_INCOMING_MESSAGE, evt.getMessage().getContent(), evt.getMessage().getContentType(), evt.getMessage().getMessageUID()); } } else if (o instanceof FileRecord) { FileRecord fileRecord = (FileRecord) o; if (!fileRecord.getID().equals(escapedMessageID)) { FileHistoryConversationComponent component = new FileHistoryConversationComponent(fileRecord); conversationPanel.addComponent(component); } } if (historyString != null) conversationPanel.appendMessageToEnd(historyString, ChatHtmlUtils.HTML_CONTENT_TYPE); } fireChatHistoryChange(); } /** * Passes the message to the contained <code>ChatConversationPanel</code> * for processing and appends it at the end of the conversationPanel * document. * * @param contactName the 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 */ public void addMessage(String contactName, Date date, String messageType, String message, String contentType) { addMessage(contactName, null, date, messageType, message, contentType, null, null); } /** * Passes the message to the contained <code>ChatConversationPanel</code> * for processing and appends it at the end of the conversationPanel * document. * * @param contactName the name of the contact sending the message * @param displayName the display name of the contact * @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 */ public void addMessage(String contactName, String displayName, Date date, String messageType, String message, String contentType, String messageUID, String correctedMessageUID) { ChatMessage chatMessage = new ChatMessage(contactName, displayName, date, messageType, null, message, contentType, messageUID, correctedMessageUID); this.addChatMessage(chatMessage); // A bug Fix for Previous/Next buttons . // Must update buttons state after message is processed // otherwise states are not proper fireChatHistoryChange(); } /** * Passes the message to the contained <code>ChatConversationPanel</code> * for processing and appends it at the end of the conversationPanel * document. * * @param contactName the 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 title the title of the message * @param message the message text * @param contentType the content type */ public void addMessage(String contactName, Date date, String messageType, String title, String message, String contentType) { ChatMessage chatMessage = new ChatMessage(contactName, date, messageType, title, message, contentType); this.addChatMessage(chatMessage); } /** * Passes the message to the contained <code>ChatConversationPanel</code> * for processing and appends it at the end of the conversationPanel * document. * * @param chatMessage the chat message to add */ private void addChatMessage(final ChatMessage chatMessage) { // We need to be sure that chat messages are added in the event dispatch // thread. if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { addChatMessage(chatMessage); } }); return; } if (ConfigurationUtils.isHistoryShown() && !isHistoryLoaded) { synchronized (incomingEventBuffer) { incomingEventBuffer.add(chatMessage); } } else { 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(); } } /** * Adds the given error message to the chat window conversation area. * * @param contactName the name of the contact, for which the error occured * @param message the error message */ public void addErrorMessage(String contactName, String message) { this.addMessage(contactName, new Date(), Chat.ERROR_MESSAGE, GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_FAILURE"), message, "text"); } /** * Adds the given error message to the chat window conversation area. * * @param contactName the name of the contact, for which the error occurred * @param title the title of the error * @param message the error message */ public void addErrorMessage(String contactName, String title, String message) { this.addMessage(contactName, new Date(), Chat.ERROR_MESSAGE, title, message, "text"); } /** * Displays the given chat message. * * @param chatMessage the chat message to display */ 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 * document. * * @param chatMessage the message to append */ private void appendChatMessage(final ChatMessage chatMessage) { String keyword = null; if (chatSession instanceof ConferenceChatSession && Chat.INCOMING_MESSAGE.equals(chatMessage.getMessageType())) { keyword = ((ChatRoomWrapper) chatSession.getDescriptor()).getChatRoom().getUserNickname(); } String processedMessage = this.conversationPanel.processMessage(chatMessage, keyword, chatSession.getCurrentChatTransport().getProtocolProvider(), chatSession.getCurrentChatTransport().getName()); if (chatSession instanceof ConferenceChatSession) { String meCommandMsg = this.conversationPanel.processMeCommand(chatMessage); // FIXME I'm pretty sure we are losing the previously prepared // processedMessage content. if (meCommandMsg.length() > 0) processedMessage = meCommandMsg; } this.conversationPanel.appendMessageToEnd(processedMessage, ChatHtmlUtils.HTML_CONTENT_TYPE); } /** * Passes the message to the contained <code>ChatConversationPanel</code> * for processing and replaces the specified message with this one. * * @param message 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. * * @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, Date date, String messageType, String message, String contentType, String messageId) { ChatMessage chatMessage = new ChatMessage(contactName, contactDisplayName, date, messageType, null, message, contentType, messageId, null); String processedMessage = this.conversationPanel.processMessage(chatMessage, chatSession.getCurrentChatTransport().getProtocolProvider(), chatSession.getCurrentChatTransport().getName()); if (chatSession instanceof ConferenceChatSession) { String tempMessage = conversationPanel.processMeCommand(chatMessage); if (tempMessage.length() > 0) processedMessage = tempMessage; } return processedMessage; } /** * Refreshes write area editor pane. Deletes all existing text * content. */ public void refreshWriteArea() { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { refreshWriteArea(); } }); return; } this.writeMessagePanel.clearWriteArea(); } /** * Adds text to the write area editor. * * @param text The text to add. */ public void addTextInWriteArea(String text) { JEditorPane editorPane = this.writeMessagePanel.getEditorPane(); editorPane.setText(editorPane.getText() + text); } /** * Returns the text contained in the write area editor. * @param mimeType the mime type * @return The text contained in the write area editor. */ public String getTextFromWriteArea(String mimeType) { if (mimeType.equals(OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE)) { return writeMessagePanel.getText(); } else { return writeMessagePanel.getTextAsHtml(); } } /** * Cuts the write area selected content to the clipboard. */ public void cut() { this.writeMessagePanel.getEditorPane().cut(); } /** * Copies either the selected write area content or the selected * conversation panel content to the clipboard. */ public void copy() { JTextComponent textPane = this.conversationPanel.getChatTextPane(); if (textPane.getSelectedText() == null) textPane = this.writeMessagePanel.getEditorPane(); textPane.copy(); } /** * Copies the selected write panel content to the clipboard. */ public void copyWriteArea() { JEditorPane editorPane = this.writeMessagePanel.getEditorPane(); editorPane.copy(); } /** * Pastes the content of the clipboard to the write area. */ public void paste() { JEditorPane editorPane = this.writeMessagePanel.getEditorPane(); editorPane.paste(); editorPane.requestFocus(); } /** * Sends current write area content. */ public void sendButtonDoClick() { if (!isWriteAreaEmpty()) { new Thread() { @Override public void run() { sendMessage(); } }.start(); } //make sure the focus goes back to the write area requestFocusInWriteArea(); } /** * Returns TRUE if this chat panel is added to a container (window or * tabbed pane), which is shown on the screen, FALSE - otherwise. * * @return TRUE if this chat panel is added to a container (window or * tabbed pane), which is shown on the screen, FALSE - otherwise */ public boolean isShown() { return isShown; } /** * Marks this chat panel as shown or hidden. * * @param isShown TRUE to mark this chat panel as shown, FALSE - otherwise */ public void setShown(boolean isShown) { this.isShown = isShown; } /** * Brings the <tt>ChatWindow</tt> containing this <tt>ChatPanel</tt> to the * front if <tt>isVisble</tt> is <tt>true</tt>; hides it, otherwise. * * @param isVisible <tt>true</tt> to bring the <tt>ChatWindow</tt> of this * <tt>ChatPanel</tt> to the front; <tt>false</tt> to close this * <tt>ChatPanel</tt> */ public void setChatVisible(boolean isVisible) { ChatWindowManager chatWindowManager = GuiActivator.getUIService().getChatWindowManager(); if (isVisible) chatWindowManager.openChat(this, isVisible); else chatWindowManager.closeChat(this); } /** * Sets the visibility of conferences panel to <tt>true</tt> or * <tt>false</tt> * * @param isVisible if <tt>true</tt> the panel is visible. */ public void setConferencesPanelVisible(boolean isVisible) { conferencePanel.setVisible(isVisible); } /** * Implements the <tt>Chat.isChatFocused</tt> method. Returns TRUE if this * chat panel is the currently selected panel and if the chat window, where * it's contained is active. * * @return true if this chat panel has the focus and false otherwise. */ public boolean isChatFocused() { ChatPanel currentChatPanel = chatContainer.getCurrentChat(); return (currentChatPanel != null && currentChatPanel.equals(this) && chatContainer.getFrame().isActive()); } /** * Adds the given {@link KeyListener} to this <tt>Chat</tt>. * The <tt>KeyListener</tt> is used to inform other bundles when a user has * typed in the chat editor area. * * @param l the <tt>KeyListener</tt> to add */ public void addChatEditorKeyListener(KeyListener l) { this.getChatWritePanel().getEditorPane().addKeyListener(l); } /** * Removes the given {@link KeyListener} from this <tt>Chat</tt>. * The <tt>KeyListener</tt> is used to inform other bundles when a user has * typed in the chat editor area. * * @param l the <tt>ChatFocusListener</tt> to remove */ public void removeChatEditorKeyListener(KeyListener l) { this.getChatWritePanel().getEditorPane().removeKeyListener(l); } /** * Returns the message written by user in the chat write area. * * @return the message written by user in the chat write area */ public String getMessage() { Document writeEditorDoc = writeMessagePanel.getEditorPane().getDocument(); try { return writeEditorDoc.getText(0, writeEditorDoc.getLength()); } catch (BadLocationException e) { return writeMessagePanel.getEditorPane().getText(); } } /** * Sets the given message as a message in the chat write area. * * @param message the text that would be set to the chat write area */ public void setMessage(String message) { writeMessagePanel.getEditorPane().setText(message); } /** * Indicates if the history of a hidden protocol should be shown to the * user in the default <b>grey</b> history style or it should be shown as * a normal message. * * @param protocolProvider the protocol provider to check * @return <code>true</code> if the given protocol is a hidden one and the * "hiddenProtocolGreyHistoryDisabled" property is set to true. */ private boolean isGreyHistoryStyleDisabled(ProtocolProviderService protocolProvider) { boolean isProtocolHidden = protocolProvider.getAccountID().isHidden(); boolean isGreyHistoryDisabled = false; String greyHistoryProperty = GuiActivator.getResources().getSettingsString("impl.gui.GREY_HISTORY_ENABLED"); if (greyHistoryProperty != null) isGreyHistoryDisabled = Boolean.parseBoolean(greyHistoryProperty); return isProtocolHidden && isGreyHistoryDisabled; } /** * Sends the given file through the currently selected chat transport by * using the given fileComponent to visualize the transfer process in the * chat conversation panel. * * @param file the file to send * @param fileComponent the file component to use for visualization */ public void sendFile(final File file, final SendFileConversationComponent fileComponent) { final ChatTransport sendFileTransport = this.findFileTransferChatTransport(); this.setSelectedChatTransport(sendFileTransport, true); if (file.length() > sendFileTransport.getMaximumFileLength()) { addMessage(chatSession.getCurrentChatTransport().getName(), new Date(), Chat.ERROR_MESSAGE, GuiActivator.getResources().getI18NString("service.gui.FILE_TOO_BIG", new String[] { sendFileTransport.getMaximumFileLength() / 1024 / 1024 + " MB" }), "", "text"); fileComponent.setFailed(); return; } SwingWorker worker = new SwingWorker() { @Override public Object construct() throws Exception { FileTransfer ft; if (writeMessagePanel.isSmsSelected()) ft = sendFileTransport.sendMultimediaFile(file); else ft = sendFileTransport.sendFile(file); final FileTransfer fileTransfer = ft; addActiveFileTransfer(fileTransfer.getID(), fileTransfer); // Add the status listener that would notify us when the file // transfer has been completed and should be removed from // active components. fileTransfer.addStatusListener(ChatPanel.this); fileComponent.setProtocolFileTransfer(fileTransfer); return ""; } @Override public void catchException(Throwable ex) { logger.error("Failed to send file.", ex); if (ex instanceof IllegalStateException) { addErrorMessage(chatSession.getCurrentChatTransport().getName(), GuiActivator.getResources().getI18NString("service.gui.MSG_SEND_CONNECTION_PROBLEM")); } else { addErrorMessage(chatSession.getCurrentChatTransport().getName(), GuiActivator.getResources() .getI18NString("service.gui.MSG_DELIVERY_ERROR", new String[] { ex.getMessage() })); } } }; worker.start(); } /** * Sends the given file through the currently selected chat transport. * * @param file the file to send */ public void sendFile(final File file) { // We need to be sure that the following code is executed in the event // dispatch thread. if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { sendFile(file); } }); return; } final ChatTransport fileTransferTransport = findFileTransferChatTransport(); // If there's no operation set we show some "not supported" messages // and we return. if (fileTransferTransport == null) { logger.error("Failed to send file."); this.addErrorMessage(chatSession.getChatName(), GuiActivator.getResources().getI18NString("service.gui.FILE_SEND_FAILED", new String[] { file.getName() }), GuiActivator.getResources().getI18NString("service.gui.FILE_TRANSFER_NOT_SUPPORTED")); return; } final SendFileConversationComponent fileComponent = new SendFileConversationComponent(this, fileTransferTransport.getDisplayName(), file); if (ConfigurationUtils.isHistoryShown() && !isHistoryLoaded) { synchronized (incomingEventBuffer) { incomingEventBuffer.add(fileComponent); } } else getChatConversationPanel().addComponent(fileComponent); this.sendFile(file, fileComponent); } /** * Sends the text contained in the write area as an SMS message or an * instance message depending on the "send SMS" check box. */ protected void sendMessage() { if (writeMessagePanel.isSmsSelected()) { this.sendSmsMessage(); } else { this.sendInstantMessage(); } } /** * Sends the text contained in the write area as an SMS message. */ public void sendSmsMessage() { String messageText = getTextFromWriteArea(OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); ChatTransport smsChatTransport = chatSession.getCurrentChatTransport(); if (!smsChatTransport.allowsSmsMessage()) { Iterator<ChatTransport> chatTransports = chatSession.getChatTransports(); while (chatTransports.hasNext()) { ChatTransport transport = chatTransports.next(); if (transport.allowsSmsMessage()) { smsChatTransport = transport; break; } } } // If there's no operation set we show some "not supported" messages // and we return. if (!smsChatTransport.allowsSmsMessage()) { logger.error("Failed to send SMS."); this.refreshWriteArea(); this.addMessage(smsChatTransport.getName(), new Date(), Chat.OUTGOING_MESSAGE, messageText, "plain/text"); this.addErrorMessage(smsChatTransport.getName(), GuiActivator.getResources().getI18NString("service.gui.SEND_SMS_NOT_SUPPORTED")); return; } // We open the send SMS dialog. SendSmsDialog smsDialog = new SendSmsDialog(this, smsChatTransport, messageText); if (smsChatTransport.askForSMSNumber()) { Object desc = smsChatTransport.getParentChatSession().getDescriptor(); // descriptor will be metacontact if (desc instanceof MetaContact) { UIPhoneUtil contactPhoneUtil = UIPhoneUtil.getPhoneUtil((MetaContact) desc); List<UIContactDetail> uiContactDetailList; boolean useAllNumbers = GuiActivator.getConfigurationService() .getBoolean(USE_ADDITIONAL_NUMBERS_PROP, false); if (useAllNumbers) uiContactDetailList = contactPhoneUtil.getAdditionalNumbers(); else uiContactDetailList = contactPhoneUtil.getAdditionalMobileNumbers(); if (uiContactDetailList.size() != 0) { SMSManager.sendSMS(this, uiContactDetailList, messageText, this); return; } } smsDialog.setPreferredSize(new Dimension(400, 200)); smsDialog.setVisible(true); } else { smsDialog.sendSmsMessage(null, messageText); smsDialog.dispose(); } } /** * Implements the <tt>ChatPanel.sendMessage</tt> method. Obtains the * appropriate operation set and sends the message, contained in the write * area, through it. */ protected void sendInstantMessage() { String htmlText; String plainText; // read the text and clear it as quick as possible // to avoid double sending if the user hits enter too quickly synchronized (writeMessagePanel) { if (isWriteAreaEmpty()) return; // Trims the html message, as it sometimes contains a lot of empty // lines, which causes some problems to some protocols. htmlText = getTextFromWriteArea(OperationSetBasicInstantMessaging.HTML_MIME_TYPE).trim(); plainText = getTextFromWriteArea(OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE).trim(); // clear the message earlier // to avoid as much as possible to not sending it twice (double enter) this.refreshWriteArea(); } String messageText; String mimeType; if (chatSession.getCurrentChatTransport() .isContentTypeSupported(OperationSetBasicInstantMessaging.HTML_MIME_TYPE) && (htmlText.indexOf("<b") > -1 || htmlText.indexOf("<i") > -1 || htmlText.indexOf("<u") > -1 || htmlText.indexOf("<font") > -1)) { messageText = htmlText; mimeType = OperationSetBasicInstantMessaging.HTML_MIME_TYPE; } else { messageText = plainText; mimeType = OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE; } try { if (isMessageCorrectionActive() && chatSession.getCurrentChatTransport().allowsMessageCorrections()) { chatSession.getCurrentChatTransport().correctInstantMessage(messageText, mimeType, correctedMessageUID); } else { chatSession.getCurrentChatTransport().sendInstantMessage(messageText, mimeType); } stopMessageCorrection(); } catch (IllegalStateException ex) { logger.error("Failed to send message.", ex); this.addMessage(chatSession.getCurrentChatTransport().getName(), new Date(), Chat.OUTGOING_MESSAGE, messageText, mimeType); String protocolError = ""; if (ex.getMessage() != null) protocolError = " " + GuiActivator.getResources().getI18NString("service.gui.ERROR_WAS", new String[] { ex.getMessage() }); this.addErrorMessage(chatSession.getCurrentChatTransport().getName(), GuiActivator.getResources().getI18NString("service.gui.MSG_SEND_CONNECTION_PROBLEM") + protocolError); } catch (Exception ex) { logger.error("Failed to send message.", ex); this.refreshWriteArea(); this.addMessage(chatSession.getCurrentChatTransport().getName(), new Date(), Chat.OUTGOING_MESSAGE, messageText, mimeType); String protocolError = ""; if (ex.getMessage() != null) protocolError = " " + GuiActivator.getResources().getI18NString("service.gui.ERROR_WAS", new String[] { ex.getMessage() }); this.addErrorMessage(chatSession.getCurrentChatTransport().getName(), GuiActivator.getResources() .getI18NString("service.gui.MSG_DELIVERY_ERROR", new String[] { protocolError })); } if (chatSession.getCurrentChatTransport().allowsTypingNotifications()) { // Send TYPING STOPPED event before sending the message getChatWritePanel().stopTypingTimer(); } } /** * Sets the property which identifies whether the chat is private messaging * chat or not. * * @param isPrivateMessagingChat if <tt>true</tt> the chat panel will be * private messaging chat panel. */ public void setPrivateMessagingChat(boolean isPrivateMessagingChat) { this.isPrivateMessagingChat = isPrivateMessagingChat; } /** * 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) { if (!showMessageInWriteArea(correctedMessageUID)) { return; } if (chatSession.getCurrentChatTransport().allowsMessageCorrections()) { this.correctedMessageUID = correctedMessageUID; Color bgColor = new Color( GuiActivator.getResources().getColor("service.gui.CHAT_EDIT_MESSAGE_BACKGROUND")); this.writeMessagePanel.setEditorPaneBackground(bgColor); } } /** * Shows the last sent message in the write area, either in order to * correct it or to send it again. * * @return <tt>true</tt> on success, <tt>false</tt> on failure. */ public boolean showLastMessageInWriteArea() { return showMessageInWriteArea(lastSentMessageUID); } /** * Shows the message with the specified ID in the write area, either * in order to correct it or to send it again. * * @param messageUID The ID of the message to show. * @return <tt>true</tt> on success, <tt>false</tt> on failure. */ public boolean showMessageInWriteArea(String messageUID) { String messageContents = conversationPanel.getMessageContents(messageUID); if (messageContents == null) { return false; } this.refreshWriteArea(); this.setMessage(messageContents); return true; } /** * 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; } /** * Returns the date of the first message in history for this chat. * * @return the date of the first message in history for this chat. */ public Date getFirstHistoryMsgTimestamp() { return firstHistoryMsgTimestamp; } /** * Returns the date of the last message in history for this chat. * * @return the date of the last message in history for this chat. */ public Date getLastHistoryMsgTimestamp() { return lastHistoryMsgTimestamp; } /** * Loads history messages ignoring the message with the specified id. * * @param escapedMessageID the id of the message to be ignored; * <tt>null</tt> if no message is to be ignored */ public void loadHistory(final String escapedMessageID) { if (!ConfigurationUtils.isHistoryShown()) { isHistoryLoaded = true; return; } SwingWorker historyWorker = new SwingWorker() { private Collection<Object> historyList; @Override public Object construct() throws Exception { // Load the history period, which initializes the // firstMessageTimestamp and the lastMessageTimeStamp variables. // Used to disable/enable history flash buttons in the chat // window tool bar. loadHistoryPeriod(); // Load the last N=CHAT_HISTORY_SIZE messages from history. historyList = chatSession.getHistory(ConfigurationUtils.getChatHistorySize()); return historyList; } /** * Called on the event dispatching thread (not on the worker thread) * after the <code>construct</code> method has returned. */ @Override public void finished() { if (historyList != null && historyList.size() > 0) { processHistory(historyList, escapedMessageID); } isHistoryLoaded = true; // Add incoming events accumulated while the history was loading // at the end of the chat. addIncomingEvents(); chatContainer.updateHistoryButtonState(ChatPanel.this); } }; historyWorker.start(); } /** * Loads history for the chat meta contact in a separate thread. Equivalent * to calling {@link #loadHistory(String)} with <tt>null</tt> for * <tt>escapedMessageID</tt>. */ public void loadHistory() { this.loadHistory(null); } /** * Loads history period dates for the current chat. */ private void loadHistoryPeriod() { this.firstHistoryMsgTimestamp = chatSession.getHistoryStartDate(); this.lastHistoryMsgTimestamp = chatSession.getHistoryEndDate(); } /** * Changes the "Send as SMS" check box state. * * @param isSmsSelected <code>true</code> to set the "Send as SMS" check box * selected, <code>false</code> - otherwise. */ public void setSmsSelected(boolean isSmsSelected) { writeMessagePanel.setSmsSelected(isSmsSelected); } /** * The <tt>ChangeProtocolAction</tt> is an <tt>AbstractAction</tt> that * opens the menu, containing all available protocol contacts. */ private class ChangeTransportAction extends AbstractAction { public void actionPerformed(ActionEvent e) { writeMessagePanel.openChatTransportSelectorBox(); } } /** * Renames all occurrences of the given <tt>chatContact</tt> in this chat * panel. * * @param chatContact the contact to rename * @param name the new name */ public void setContactName(ChatContact<?> chatContact, String name) { if (chatContactListPanel != null) { chatContactListPanel.renameContact(chatContact); } ChatContainer chatContainer = getChatContainer(); chatContainer.setChatTitle(this, name); if (chatContainer.getCurrentChat() == this) { chatContainer.setTitle(name); } } /** * Adds the given chatTransport to the given send via selector box. * * @param chatTransport the transport to add */ public void addChatTransport(ChatTransport chatTransport) { writeMessagePanel.addChatTransport(chatTransport); } /** * Removes the given chat status state from the send via selector box. * * @param chatTransport the transport to remove */ public void removeChatTransport(ChatTransport chatTransport) { writeMessagePanel.removeChatTransport(chatTransport); } /** * Selects the given chat transport in the send via box. * * @param chatTransport the chat transport to be selected * @param isMessageOrFileTransferReceived Boolean telling us if this change * of the chat transport correspond to an effective switch to this new * transform (a message received from this transport, or a file transfer * request received, or if the resource timeouted), or just a status update * telling us a new chatTransport is now available (i.e. another device has * startup). */ public void setSelectedChatTransport(ChatTransport chatTransport, boolean isMessageOrFileTransferReceived) { writeMessagePanel.setSelectedChatTransport(chatTransport, isMessageOrFileTransferReceived); } /** * Updates the status of the given chat transport in the send via selector * box and notifies the user for the status change. * @param chatTransport the <tt>chatTransport</tt> to update */ public void updateChatTransportStatus(final ChatTransport chatTransport) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { updateChatTransportStatus(chatTransport); } }); return; } writeMessagePanel.updateChatTransportStatus(chatTransport); if (!chatTransport.equals(chatSession.getCurrentChatTransport())) return; if (ConfigurationUtils.isShowStatusChangedInChat()) { // Show a status message to the user. this.addMessage(chatTransport.getName(), new Date(), Chat.STATUS_MESSAGE, GuiActivator.getResources().getI18NString("service.gui.STATUS_CHANGED_CHAT_MESSAGE", new String[] { chatTransport.getStatus().getStatusName() }), "text/plain"); } } /** * Sets the chat icon. * * @param icon the chat icon to set */ public void setChatIcon(Icon icon) { if (ConfigurationUtils.isMultiChatWindowEnabled()) { if (getChatContainer().getChatCount() > 0) { getChatContainer().setChatIcon(this, icon); } } } /** * Implements <tt>ChatPanel.loadPreviousFromHistory</tt>. * Loads previous page from history. */ public void loadPreviousPageFromHistory() { final MetaHistoryService chatHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history service could be "disabled" from the user // through one of the configuration forms. if (chatHistory == null) return; SwingWorker worker = new SwingWorker() { @Override public Object construct() throws Exception { ChatConversationPanel conversationPanel = getChatConversationPanel(); Date firstMsgDate = conversationPanel.getPageFirstMsgTimestamp(); Collection<Object> c = null; if (firstMsgDate != null) { c = chatSession.getHistoryBeforeDate(firstMsgDate, MESSAGES_PER_PAGE); } if (c != null && c.size() > 0) { SwingUtilities.invokeLater(new HistoryMessagesLoader(c)); } return ""; } @Override public void finished() { getChatContainer().updateHistoryButtonState(ChatPanel.this); } }; worker.start(); } /** * Implements <tt>ChatPanel.loadNextFromHistory</tt>. * Loads next page from history. */ public void loadNextPageFromHistory() { final MetaHistoryService chatHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (chatHistory == null) return; SwingWorker worker = new SwingWorker() { @Override public Object construct() throws Exception { Date lastMsgDate = getChatConversationPanel().getPageLastMsgTimestamp(); Collection<Object> c = null; if (lastMsgDate != null) { c = chatSession.getHistoryAfterDate(lastMsgDate, MESSAGES_PER_PAGE); } if (c != null && c.size() > 0) SwingUtilities.invokeLater(new HistoryMessagesLoader(c)); return ""; } @Override public void finished() { getChatContainer().updateHistoryButtonState(ChatPanel.this); } }; worker.start(); } /** * From a given collection of messages shows the history in the chat window. */ private class HistoryMessagesLoader implements Runnable { private final Collection<Object> chatHistory; public HistoryMessagesLoader(Collection<Object> history) { this.chatHistory = history; } public void run() { ChatConversationPanel chatConversationPanel = getChatConversationPanel(); chatConversationPanel.clear(); processHistory(chatHistory, ""); chatConversationPanel.setDefaultContent(); } } /** * Adds the given <tt>chatContact</tt> to the list of chat contacts * participating in the corresponding to this chat panel chat. * @param chatContact the contact to add */ public void addChatContact(ChatContact<?> chatContact) { if (chatContactListPanel != null) chatContactListPanel.addContact(chatContact); } /** * Removes the given <tt>chatContact</tt> from the list of chat contacts * participating in the corresponding to this chat panel chat. * @param chatContact the contact to remove */ public void removeChatContact(ChatContact<?> chatContact) { if (chatContactListPanel != null) chatContactListPanel.removeContact(chatContact); } /** * Adds the given <tt>conferenceDescription</tt> to the list of chat * conferences in this chat panel chat. * @param conferenceDescription the conference to add. */ @Override public void addChatConferenceCall(ConferenceDescription conferenceDescription) { if (chatConferencesDialog != null) { chatConferencesDialog.addConference(conferenceDescription); } } /** * Removes the given <tt>conferenceDescription</tt> from the list of chat * conferences in this chat panel chat. * @param conferenceDescription the conference to remove. */ @Override public void removeChatConferenceCall(ConferenceDescription conferenceDescription) { if (chatConferencesDialog != null) { chatConferencesDialog.removeConference(conferenceDescription); } } /** * Removes all chat contacts from the contact list of the chat. */ public void removeAllChatContacts() { if (chatContactListPanel != null) chatContactListPanel.removeAllChatContacts(); } /** * Updates the contact status. * @param chatContact the chat contact to update * @param statusMessage the status message to show */ public void updateChatContactStatus(final ChatContact<?> chatContact, final String statusMessage) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { updateChatContactStatus(chatContact, statusMessage); } }); return; } this.addMessage(chatContact.getName(), new Date(), Chat.STATUS_MESSAGE, statusMessage, ChatHtmlUtils.TEXT_CONTENT_TYPE); } /** * Sets the given <tt>subject</tt> to this chat. * @param subject the subject to set */ public void setChatSubject(final String subject) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { setChatSubject(subject); } }); return; } if (subjectPanel != null) { // Don't do anything if the subject doesn't really change. String oldSubject = subjectPanel.getSubject(); if ((subject == null) || (subject.length() == 0)) { if ((oldSubject == null) || (oldSubject.length() == 0)) return; } else if (subject.equals(oldSubject)) return; subjectPanel.setSubject(subject); this.addMessage(chatSession.getChatName(), new Date(), Chat.STATUS_MESSAGE, GuiActivator.getResources().getI18NString("service.gui.CHAT_ROOM_SUBJECT_CHANGED", new String[] { chatSession.getChatName(), subject }), ChatHtmlUtils.TEXT_CONTENT_TYPE); } } /** * Adds the given <tt>IncomingFileTransferRequest</tt> to the conversation * panel in order to notify the user of the incoming file. * * @param fileTransferOpSet the file transfer operation set * @param request the request to display in the conversation panel * @param date the date on which the request has been received */ public void addIncomingFileTransferRequest(final OperationSetFileTransfer fileTransferOpSet, final IncomingFileTransferRequest request, final Date date) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { addIncomingFileTransferRequest(fileTransferOpSet, request, date); } }); return; } this.addActiveFileTransfer(request.getID(), request); ReceiveFileConversationComponent component = new ReceiveFileConversationComponent(this, fileTransferOpSet, request, date); if (ConfigurationUtils.isHistoryShown() && !isHistoryLoaded) { synchronized (incomingEventBuffer) { incomingEventBuffer.add(component); } } else this.getChatConversationPanel().addComponent(component); } /** * Implements <tt>Chat.addChatFocusListener</tt> method. Adds the given * <tt>ChatFocusListener</tt> to the list of listeners. * * @param listener the listener that we'll be adding. */ public void addChatFocusListener(ChatFocusListener listener) { synchronized (focusListeners) { if (!focusListeners.contains(listener)) focusListeners.add(listener); } } /** * Implements <tt>Chat.removeChatFocusListener</tt> method. Removes the given * <tt>ChatFocusListener</tt> from the list of listeners. * * @param listener the listener to remove. */ public void removeChatFocusListener(ChatFocusListener listener) { synchronized (focusListeners) { focusListeners.remove(listener); } } /** * Returns the first chat transport for the current chat session that * supports file transfer. * * @return the first chat transport for the current chat session that * supports file transfer. */ public ChatTransport findFileTransferChatTransport() { // We currently don't support file transfer in group chats. if (chatSession instanceof ConferenceChatSession) return null; ChatTransport currentChatTransport = chatSession.getCurrentChatTransport(); if (currentChatTransport.getProtocolProvider().getOperationSet(OperationSetFileTransfer.class) != null) { return currentChatTransport; } else { Iterator<ChatTransport> chatTransportsIter = chatSession.getChatTransports(); while (chatTransportsIter.hasNext()) { ChatTransport chatTransport = chatTransportsIter.next(); Object fileTransferOpSet = chatTransport.getProtocolProvider() .getOperationSet(OperationSetFileTransfer.class); if (fileTransferOpSet != null) return chatTransport; } } return null; } /** * Returns the first chat transport for the current chat session that * supports group chat. * * @return the first chat transport for the current chat session that * supports group chat. */ public ChatTransport findInviteChatTransport() { ChatTransport currentChatTransport = chatSession.getCurrentChatTransport(); ProtocolProviderService protocolProvider = currentChatTransport.getProtocolProvider(); // We choose between OpSets for multi user chat... if (protocolProvider.getOperationSet(OperationSetMultiUserChat.class) != null || protocolProvider.getOperationSet(OperationSetAdHocMultiUserChat.class) != null) { return chatSession.getCurrentChatTransport(); } else { Iterator<ChatTransport> chatTransportsIter = chatSession.getChatTransports(); while (chatTransportsIter.hasNext()) { ChatTransport chatTransport = chatTransportsIter.next(); Object groupChatOpSet = chatTransport.getProtocolProvider() .getOperationSet(OperationSetMultiUserChat.class); if (groupChatOpSet != null) return chatTransport; } } return null; } /** * Invites the given <tt>chatContacts</tt> to this chat. * @param inviteChatTransport the chat transport to use to send the invite * @param chatContacts the contacts to invite * @param reason the reason of the invitation */ public void inviteContacts(ChatTransport inviteChatTransport, Collection<String> chatContacts, String reason) { ChatSession conferenceChatSession = null; if (chatSession instanceof MetaContactChatSession) { chatContacts.add(inviteChatTransport.getName()); ConferenceChatManager conferenceChatManager = GuiActivator.getUIService().getConferenceChatManager(); // the chat session is set regarding to which OpSet is used for MUC if (inviteChatTransport.getProtocolProvider() .getOperationSet(OperationSetMultiUserChat.class) != null) { ChatRoomWrapper chatRoomWrapper = GuiActivator.getMUCService().createPrivateChatRoom( inviteChatTransport.getProtocolProvider(), chatContacts, reason, false); conferenceChatSession = new ConferenceChatSession(this, chatRoomWrapper); } else if (inviteChatTransport.getProtocolProvider() .getOperationSet(OperationSetAdHocMultiUserChat.class) != null) { AdHocChatRoomWrapper chatRoomWrapper = conferenceChatManager .createAdHocChatRoom(inviteChatTransport.getProtocolProvider(), chatContacts, reason); conferenceChatSession = new AdHocConferenceChatSession(this, chatRoomWrapper); } if (conferenceChatSession != null) this.setChatSession(conferenceChatSession); } // We're already in a conference chat. else { conferenceChatSession = chatSession; for (String contactAddress : chatContacts) { conferenceChatSession.getCurrentChatTransport().inviteChatContact(contactAddress, reason); } } } /** * Informs all <tt>ChatFocusListener</tt>s that a <tt>ChatFocusEvent</tt> * has been triggered. * * @param eventID the type of the <tt>ChatFocusEvent</tt> */ public void fireChatFocusEvent(int eventID) { ChatFocusEvent evt = new ChatFocusEvent(this, eventID); if (logger.isTraceEnabled()) logger.trace("Will dispatch the following chat event: " + evt); Iterable<ChatFocusListener> listeners; synchronized (focusListeners) { listeners = new ArrayList<ChatFocusListener>(focusListeners); } for (ChatFocusListener listener : listeners) { switch (evt.getEventID()) { case ChatFocusEvent.FOCUS_GAINED: listener.chatFocusGained(evt); break; case ChatFocusEvent.FOCUS_LOST: listener.chatFocusLost(evt); break; default: logger.error("Unknown event type " + evt.getEventID()); } } } /** * Handles file transfer status changed in order to remove completed file * transfers from the list of active transfers. * @param event the file transfer status change event the notified us for * the change */ public void statusChanged(FileTransferStatusChangeEvent event) { FileTransfer fileTransfer = event.getFileTransfer(); int newStatus = event.getNewStatus(); if (newStatus == FileTransferStatusChangeEvent.COMPLETED || newStatus == FileTransferStatusChangeEvent.CANCELED || newStatus == FileTransferStatusChangeEvent.FAILED || newStatus == FileTransferStatusChangeEvent.REFUSED) { removeActiveFileTransfer(fileTransfer.getID()); fileTransfer.removeStatusListener(this); } } /** * Returns <code>true</code> if there are active file transfers, otherwise * returns <code>false</code>. * @return <code>true</code> if there are active file transfers, otherwise * returns <code>false</code> */ public boolean containsActiveFileTransfers() { return !activeFileTransfers.isEmpty(); } /** * Cancels all active file transfers. */ public void cancelActiveFileTransfers() { Enumeration<String> activeKeys = activeFileTransfers.keys(); while (activeKeys.hasMoreElements()) { // catchall so if anything happens we still // will close the chat/window try { String key = activeKeys.nextElement(); Object descriptor = activeFileTransfers.get(key); if (descriptor instanceof IncomingFileTransferRequest) { ((IncomingFileTransferRequest) descriptor).rejectFile(); } else if (descriptor instanceof FileTransfer) { ((FileTransfer) descriptor).cancel(); } } catch (Throwable t) { logger.error("Cannot cancel file transfer.", t); } } } /** * Stores the current divider position. */ private class DividerLocationListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) { int dividerLocation = (Integer) evt.getNewValue(); // We store the divider location only when the user drags the // divider and not when we've set it programatically. // if (dividerLocation != autoDividerLocation) // { int writeAreaSize = messagePane.getHeight() - dividerLocation - messagePane.getDividerSize(); ConfigurationUtils.setChatWriteAreaSize(writeAreaSize); // writeMessagePanel.setPreferredSize( // new Dimension( // (int) writeMessagePanel.getPreferredSize() // .getWidth(), // writeAreaSize)); // } } } } /** * Sets the location of the split pane divider. * * @param location the location of the divider given by the pixel count * between the left bottom corner and the left bottom divider location */ public void setDividerLocation(int location) { int dividerLocation = messagePane.getHeight() - location; messagePane.setDividerLocation(dividerLocation); messagePane.revalidate(); messagePane.repaint(); } /** * Returns the contained divider location. * * @return the contained divider location */ public int getDividerLocation() { return messagePane.getHeight() - messagePane.getDividerLocation(); } /** * Returns the contained divider size. * * @return the contained divider size */ public int getDividerSize() { return messagePane.getDividerSize(); } /** * Adds all events accumulated in the incoming event buffer to the * chat conversation panel. */ private void addIncomingEvents() { synchronized (incomingEventBuffer) { Iterator<Object> eventBufferIter = incomingEventBuffer.iterator(); while (eventBufferIter.hasNext()) { Object incomingEvent = eventBufferIter.next(); if (incomingEvent instanceof ChatMessage) { this.displayChatMessage((ChatMessage) incomingEvent); } else if (incomingEvent instanceof ChatConversationComponent) { this.getChatConversationPanel().addComponent((ChatConversationComponent) incomingEvent); } } } } /** * Adds the given file transfer <tt>id</tt> to the list of active file * transfers. * * @param id the identifier of the file transfer to add * @param descriptor the descriptor of the file transfer */ public void addActiveFileTransfer(String id, Object descriptor) { synchronized (activeFileTransfers) { activeFileTransfers.put(id, descriptor); } } /** * Removes the given file transfer <tt>id</tt> from the list of active * file transfers. * @param id the identifier of the file transfer to remove */ public void removeActiveFileTransfer(String id) { synchronized (activeFileTransfers) { activeFileTransfers.remove(id); } } /** * Adds the given {@link ChatMenuListener} to this <tt>Chat</tt>. * The <tt>ChatMenuListener</tt> is used to determine menu elements * that should be added on right clicks. * * @param l the <tt>ChatMenuListener</tt> to add */ public void addChatEditorMenuListener(ChatMenuListener l) { this.getChatWritePanel().addChatEditorMenuListener(l); } /** * Adds the given {@link CaretListener} to this <tt>Chat</tt>. * The <tt>CaretListener</tt> is used to inform other bundles when a user has * moved the caret in the chat editor area. * * @param l the <tt>CaretListener</tt> to add */ public void addChatEditorCaretListener(CaretListener l) { this.getChatWritePanel().getEditorPane().addCaretListener(l); } /** * Adds the given {@link DocumentListener} to this <tt>Chat</tt>. * The <tt>DocumentListener</tt> is used to inform other bundles when a user has * modified the document in the chat editor area. * * @param l the <tt>DocumentListener</tt> to add */ public void addChatEditorDocumentListener(DocumentListener l) { this.getChatWritePanel().getEditorPane().getDocument().addDocumentListener(l); } /** * Removes the given {@link CaretListener} from this <tt>Chat</tt>. * The <tt>CaretListener</tt> is used to inform other bundles when a user has * moved the caret in the chat editor area. * * @param l the <tt>CaretListener</tt> to remove */ public void removeChatEditorCaretListener(CaretListener l) { this.getChatWritePanel().getEditorPane().removeCaretListener(l); } /** * Removes the given {@link ChatMenuListener} to this <tt>Chat</tt>. * The <tt>ChatMenuListener</tt> is used to determine menu elements * that should be added on right clicks. * * @param l the <tt>ChatMenuListener</tt> to add */ public void removeChatEditorMenuListener(ChatMenuListener l) { this.getChatWritePanel().removeChatEditorMenuListener(l); } /** * Removes the given {@link DocumentListener} from this <tt>Chat</tt>. * The <tt>DocumentListener</tt> is used to inform other bundles when a user has * modified the document in the chat editor area. * * @param l the <tt>DocumentListener</tt> to remove */ public void removeChatEditorDocumentListener(DocumentListener l) { this.getChatWritePanel().getEditorPane().getDocument().removeDocumentListener(l); } /** * Adds the given <tt>ChatHistoryListener</tt> to the list of listeners * notified when a change occurs in the history shown in this chat panel. * * @param l the <tt>ChatHistoryListener</tt> to add */ public void addChatHistoryListener(ChatHistoryListener l) { synchronized (historyListeners) { historyListeners.add(l); } } /** * Removes the given <tt>ChatHistoryListener</tt> from the list of listeners * notified when a change occurs in the history shown in this chat panel. * * @param l the <tt>ChatHistoryListener</tt> to remove */ public void removeChatHistoryListener(ChatHistoryListener l) { synchronized (historyListeners) { historyListeners.remove(l); } } /** * Notifies all registered <tt>ChatHistoryListener</tt>s that a change has * occurred in the history of this chat. */ private void fireChatHistoryChange() { Iterator<ChatHistoryListener> listeners = historyListeners.iterator(); while (listeners.hasNext()) { listeners.next().chatHistoryChanged(this); } } /** * Provides the {@link Highlighter} used in rendering the chat editor. * * @return highlighter used to render message being composed */ public Highlighter getHighlighter() { return this.getChatWritePanel().getEditorPane().getHighlighter(); } /** * Gets the caret position in the chat editor. * @return index of caret in message being composed */ public int getCaretPosition() { return this.getChatWritePanel().getEditorPane().getCaretPosition(); } /** * Causes the chat to validate its appearance (suggests a repaint operation * may be necessary). */ public void promptRepaint() { this.getChatWritePanel().getEditorPane().repaint(); } /** * Shows the font chooser dialog */ public void showFontChooserDialog() { JEditorPane editorPane = writeMessagePanel.getEditorPane(); FontChooser fontChooser = new FontChooser(); int result = fontChooser.showDialog(this); if (result != FontChooser.CANCEL_OPTION) { String fontFamily = fontChooser.getFontFamily(); int fontSize = fontChooser.getFontSize(); boolean isBold = fontChooser.isBoldStyleSelected(); boolean isItalic = fontChooser.isItalicStyleSelected(); boolean isUnderline = fontChooser.isUnderlineStyleSelected(); Color fontColor = fontChooser.getFontColor(); // Font family and size writeMessagePanel.setFontFamilyAndSize(fontFamily, fontSize); // Font style writeMessagePanel.setBoldStyleEnable(isBold); writeMessagePanel.setItalicStyleEnable(isItalic); writeMessagePanel.setUnderlineStyleEnable(isUnderline); // Font color writeMessagePanel.setFontColor(fontColor); writeMessagePanel.saveDefaultFontConfiguration(fontFamily, fontSize, isBold, isItalic, isUnderline, fontColor); } editorPane.requestFocus(); } /** * Reloads chat messages. */ public void loadSkin() { getChatConversationPanel().clear(); loadHistory(); getChatConversationPanel().setDefaultContent(); } /** * Notifies the user if any member of the chatroom changes nickname. * * @param event a <tt>ChatRoomMemberPropertyChangeEvent</tt> which carries * the specific of the change */ public void chatRoomPropertyChanged(ChatRoomMemberPropertyChangeEvent event) { if (ChatRoomMemberPropertyChangeEvent.MEMBER_NICKNAME.equals(event.getPropertyName())) { String message = GuiActivator.getResources().getI18NString("service.gui.CHAT_NICKNAME_CHANGE", new String[] { (String) event.getOldValue(), (String) event.getNewValue() }); this.conversationPanel.appendMessageToEnd("<DIV identifier=\"message\" style=\"color:#707070;\">" + StringEscapeUtils.escapeHtml4(message) + "</DIV>", ChatHtmlUtils.HTML_CONTENT_TYPE); } } /** * Add a new ChatLinkClickedListener * * @param listener ChatLinkClickedListener */ public void addChatLinkClickedListener(ChatLinkClickedListener listener) { conversationPanel.addChatLinkClickedListener(listener); } /** * Remove existing ChatLinkClickedListener * * @param listener ChatLinkClickedListener */ public void removeChatLinkClickedListener(ChatLinkClickedListener listener) { conversationPanel.removeChatLinkClickedListener(listener); } /** * Changes the chat conference dialog layout. This method is called when the * local user publishes a <tt>ConferenceDescription</tt> instance. * * @param conferenceDescription the <tt>ConferenceDescription</tt> instance * associated with the conference. */ @Override public void chatConferenceDescriptionSent(ConferenceDescription conferenceDescription) { boolean available = conferenceDescription.isAvailable(); chatConferencesDialog.setCreatePanelEnabled(!available); chatConferencesDialog.setEndConferenceButtonEnabled(available); } }