0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.collab.channel.chat;
0042:
0043: import com.sun.collablet.CollabException;
0044: import com.sun.collablet.CollabManager;
0045: import com.sun.collablet.CollabMessage;
0046: import com.sun.collablet.CollabPrincipal;
0047: import com.sun.collablet.ContentTypes;
0048: import com.sun.collablet.Conversation;
0049: import com.sun.collablet.ConversationPrivilege;
0050: import com.sun.collablet.UserInterface;
0051: import com.sun.collablet.chat.ChatCollablet;
0052: import java.awt.GridBagConstraints;
0053: import java.awt.GridBagLayout;
0054: import org.openide.ErrorManager;
0055:
0056: import org.openide.awt.*;
0057: import org.openide.util.*;
0058: import org.openide.windows.*;
0059:
0060: import java.awt.BorderLayout;
0061: import java.awt.Color;
0062: import java.awt.Dimension;
0063: import java.awt.Font;
0064: import java.awt.FontMetrics;
0065: import java.awt.Graphics;
0066: import java.awt.GridLayout;
0067: import java.awt.Image;
0068: import java.awt.Rectangle;
0069: import java.awt.Shape;
0070: import java.awt.datatransfer.*;
0071: import java.awt.event.*;
0072:
0073: import java.beans.*;
0074:
0075: import java.io.*;
0076:
0077: import java.lang.reflect.*;
0078:
0079: import java.net.URL;
0080:
0081: import java.text.*;
0082:
0083: import java.util.*;
0084:
0085: import javax.swing.*;
0086: import javax.swing.SwingUtilities;
0087: import javax.swing.border.*;
0088: import javax.swing.event.*;
0089: import javax.swing.plaf.basic.BasicMenuItemUI;
0090: import javax.swing.text.*;
0091: import javax.swing.text.html.*;
0092:
0093: import org.netbeans.modules.collab.*;
0094: import org.netbeans.modules.collab.channel.chat.messagetype.CollabContentType;
0095: import org.netbeans.modules.collab.core.Debug;
0096:
0097: /**
0098: *
0099: *
0100: * @author Todd Fast, todd.fast@sun.com
0101: */
0102: public class ChatComponent extends JPanel implements HyperlinkListener {
0103: ////////////////////////////////////////////////////////////////////////////
0104: // Class variables
0105: ////////////////////////////////////////////////////////////////////////////
0106: private static final long serialVersionUID = 1L;
0107: private static final String END_ELEMENT_ID = "END";
0108: private static final String MESSAGE_TEMPLATE_ELEMENT_ID = "MESSAGE_TEMPLATE";
0109: private static final String SYSTEM_MESSAGE_TEMPLATE_ELEMENT_ID = "SYSTEM_MESSAGE_TEMPLATE";
0110: private static final String CHAT_MESSAGE_TEMPLATE_ELEMENT_ID = "CHAT_MESSAGE_TEMPLATE";
0111: private static final String CHAT_TEMPLATE_RESOURCE = "/org/netbeans/modules/collab/channel/chat/chat_template.html";
0112: private static String[][] smiles = new String[][] {
0113: { ":-)", "emo_smiley16.png" },
0114: { ":)", "emo_smiley16.png" }, { ":-(", "emo_sad16.png" },
0115: { ":(", "emo_sad16.png" }, { ";-)", "emo_wink16.png" },
0116: { ";)", "emo_wink16.png" },
0117: { ":=)", "emo_laughing16.png" },
0118: { "8-)", "emo_cool16.png" }, { ":-D", "emo_grin16.png" } };
0119:
0120: // NOTE: These constants must be kept in sync with the values in the
0121: // HTML template file!
0122: private static final String TOKEN_SENDER_CLASS = "__SENDER_CLASS__";
0123: private static final String TOKEN_SENDER = "__SENDER__";
0124: private static final String TOKEN_MESSAGE_CLASS = "__MESSAGE_CLASS__";
0125: private static final String TOKEN_MESSAGE = "__MESSAGE__";
0126: private static final String TOKEN_MESSAGE_TEXT = "__MESSAGE_TEXT__";
0127: private static final String TOKEN_TIMESTAMP = "__TIMESTAMP__";
0128: private static final String TOKEN_CONTENT_TYPE = "__CONTENT_TYPE__";
0129: private static final String MESSAGE_CLASS_CHAT = "message-chat";
0130: private static final String MESSAGE_CLASS_TEXT = "message-text";
0131: private static final String MESSAGE_CLASS_HTML = "message-html";
0132: private static final String MESSAGE_CLASS_SYSTEM = "message-system";
0133: private static final String MESSAGE_CLASS_OTHER = "message-other";
0134: private static final String MESSAGE_CLASS_XML = "message-xml";
0135: private static final String MESSAGE_CLASS_JAVA = "message-java";
0136: private static final String DEFAULT_SENDER_CLASS = "sender-default";
0137: private static final int MAX_SENDER_CLASSES = 9;
0138: private static final String TOKEN_BASE_FONT_SIZE = "@BASE_FONT_SIZE@";
0139: private static final String TOKEN_SMALL_FONT_SIZE = "@SMALL_FONT_SIZE@";
0140:
0141: ////////////////////////////////////////////////////////////////////////////
0142: // Instance variables
0143: ////////////////////////////////////////////////////////////////////////////
0144: private ChatCollablet channel;
0145: private JEditorPane transcriptPane;
0146: private ChatInputPane inputPane;
0147: private JButton sendButton;
0148: private JLabel typingMessageLabel;
0149: private String inputContentType = ContentTypes.UNKNOWN_TEXT;
0150: private String previouslySelectedContentType;
0151: private Element endElement;
0152: private String messageTemplate;
0153: private String chatMessageTemplate;
0154: private String systemMessageTemplate;
0155: private Map senderStyles = new HashMap();
0156: private Map contentTypeToButton = new HashMap();
0157: private int lastSenderStyleIndex;
0158: private ConversationChangeListener conversationListener;
0159: private javax.swing.Timer typingTimer = null;
0160: private int TYPE_TIMER_INTERVAL = 2000;
0161: private Set typingParticipants = Collections
0162: .synchronizedSet(new HashSet());
0163: private JPopupMenu popupMenu;
0164: private JPopupMenu newMenu;
0165: private JButton smileyButton;
0166: private JToggleButton chatFocusButton;
0167:
0168: /**
0169: *
0170: *
0171: */
0172: public ChatComponent(ChatCollablet channel) {
0173: super ();
0174: this .channel = channel;
0175:
0176: initialize();
0177: initTypingTimer();
0178:
0179: // Listen to conversation changes
0180: conversationListener = new ConversationChangeListener();
0181: channel.addPropertyChangeListener(conversationListener);
0182: channel.getConversation().addPropertyChangeListener(
0183: conversationListener);
0184:
0185: // To update Find, Copy, etc. actions, add to constructor:
0186: // ActionMap map = getActionMap();
0187: // Action findBinding = new MyFindAction(this);
0188: // map.put(((CallbackSystemAction)SystemAction.get(
0189: // FindAction.class)).getActionMapKey(), findBinding);
0190: // Ensure that the first sender style is reserved for us
0191: lastSenderStyleIndex++;
0192: allocateNewSenderStyleClass(channel.getConversation()
0193: .getCollabSession().getUserPrincipal().getDisplayName());
0194:
0195: if (chatFocusButton != null) {
0196: chatFocusButton.setSelected(true);
0197: }
0198:
0199: setHelpCtx();
0200: }
0201:
0202: /**
0203: *set help ctx map id for context sensitive help
0204: *
0205: */
0206: private void setHelpCtx() {
0207: HelpCtx.setHelpIDString(this , "collab_chat_overview"); //NOI18n
0208: }
0209:
0210: /**
0211: *
0212: *
0213: */
0214: private void initialize() {
0215: // Set our layout--if we don't do this, nothing will appear in
0216: // the component
0217: setLayout(new BorderLayout());
0218:
0219: JSplitPane splitPanel = new JSplitPane(
0220: JSplitPane.VERTICAL_SPLIT, true);
0221: splitPanel.setResizeWeight(0.85f);
0222: splitPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
0223:
0224: // We must set the editor kit explicitly in order to keep NetBeans
0225: // from installing its own document type
0226: transcriptPane = new JEditorPane();
0227: transcriptPane.setEditable(false);
0228:
0229: // Install our own editor kit in order to ensure rendering of the
0230: // message template element is no-op'd
0231: transcriptPane.setEditorKit(new HTMLEditorKit() {
0232: public ViewFactory getViewFactory() {
0233: return new ChatHTMLFactory();
0234: }
0235: });
0236:
0237: createPopupMenu();
0238: createSmileMenu();
0239:
0240: initializeTranscriptTemplate();
0241:
0242: // Listen to clicked links
0243: transcriptPane.addHyperlinkListener(this );
0244:
0245: // Create a scroll pane to contain the conversation transcript
0246: JScrollPane transcriptScrollPane1 = new JScrollPane(
0247: transcriptPane);
0248: transcriptScrollPane1
0249: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
0250:
0251: JPanel transcriptPanel = new JPanel(new BorderLayout());
0252: transcriptPanel.add(transcriptScrollPane1, BorderLayout.CENTER);
0253: splitPanel.add(transcriptPanel, JSplitPane.TOP);
0254:
0255: // Panel to hold the various input components
0256: JPanel inputPanel = new JPanel();
0257: inputPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
0258: inputPanel.setLayout(new GridBagLayout());
0259:
0260: boolean canSendMessages = false;
0261:
0262: try {
0263: if ((getChannel().getConversation().getPrivilege() == ConversationPrivilege.MANAGE)
0264: || (getChannel().getConversation().getPrivilege() == ConversationPrivilege.WRITE)) {
0265: canSendMessages = true;
0266: }
0267: } catch (CollabException e) {
0268: canSendMessages = false;
0269: }
0270:
0271: if (canSendMessages) {
0272: // Create a send button
0273: sendButton = new JButton() {
0274: public void transferFocusBackward() {
0275: // Make sure the prior component is always the input pane
0276: getInputPane().requestFocus();
0277: }
0278: };
0279: Mnemonics.setLocalizedText(sendButton, NbBundle
0280: .getMessage(ChatComponent.class,
0281: "LBL_ChatComponent_SendButton"));
0282: sendButton
0283: .addActionListener(new SendButtonActionListener());
0284:
0285: // Add the toolbar
0286: GridBagConstraints gbc = new GridBagConstraints();
0287: gbc.fill = gbc.HORIZONTAL;
0288: gbc.gridx = 0;
0289: gbc.gridy = 1;
0290: gbc.weightx = 0.0;
0291: gbc.weighty = 0.0;
0292: inputPanel.add(initializeToolbar(), gbc);
0293: gbc = new GridBagConstraints();
0294: gbc.fill = gbc.NONE;
0295: gbc.anchor = gbc.EAST;
0296: gbc.gridx = 2;
0297: gbc.gridy = 1;
0298: gbc.weightx = 0.0;
0299: gbc.weighty = 0.0;
0300: inputPanel.add(sendButton, gbc);
0301:
0302: // Create an input pane
0303: inputPane = new ChatInputPane(this );
0304: inputPane.setEditable(true);
0305: inputPane._setContentType(inputContentType);
0306: inputPane.setTransferHandler(new InputPaneTransferHandler(
0307: inputPane.getTransferHandler()));
0308:
0309: KeyStroke snd = KeyStroke.getKeyStroke("control ENTER"); // NOI18N
0310: inputPane.getInputMap().put(snd, "sendAction"); // NOI18N
0311: inputPane.getActionMap().put("sendAction",
0312: new SendButtonActionListener());
0313:
0314: // registers itself
0315: InputPaneDocumentListener lst = new InputPaneDocumentListener();
0316:
0317: // Create a scroll pane to contain the input pane
0318: JScrollPane inputScrollPane = new JScrollPane(inputPane);
0319: inputScrollPane
0320: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
0321: gbc = new GridBagConstraints();
0322: gbc.fill = gbc.BOTH;
0323: gbc.gridx = 0;
0324: gbc.gridy = 0;
0325: gbc.gridwidth = 3;
0326: gbc.weightx = 1.0;
0327: gbc.weighty = 1.0;
0328: inputPanel.add(inputScrollPane, gbc);
0329:
0330: typingMessageLabel = new JLabel();
0331: gbc = new GridBagConstraints();
0332: gbc.fill = gbc.HORIZONTAL;
0333: gbc.anchor = gbc.CENTER;
0334: gbc.gridx = 1;
0335: gbc.weightx = 1.0;
0336: gbc.weighty = 0.0;
0337: inputPanel.add(typingMessageLabel, gbc);
0338: } else {
0339: // Instead, add a label that indicates that the user cannot
0340: // send messages in this conversation
0341: JLabel messageLabel = new JLabel(NbBundle.getMessage(
0342: ChatComponent.class,
0343: "LBL_ChatComponent_NoPrivilege"), // NOI18N
0344: SwingConstants.CENTER);
0345: GridBagConstraints gbc = new GridBagConstraints();
0346: gbc.fill = gbc.HORIZONTAL;
0347: gbc.gridx = 0;
0348: gbc.gridy = 0;
0349: gbc.weightx = 1.0;
0350: gbc.weighty = 0.0;
0351: inputPanel.add(messageLabel, gbc);
0352: typingMessageLabel = new JLabel();
0353:
0354: typingMessageLabel.setOpaque(true);
0355: gbc.fill = gbc.HORIZONTAL;
0356: gbc.gridy = 1;
0357: gbc.weightx = 1.0;
0358: gbc.weighty = 0.0;
0359: inputPanel.add(typingMessageLabel, gbc);
0360: }
0361:
0362: splitPanel.add(inputPanel, JSplitPane.BOTTOM);
0363:
0364: add(splitPanel, BorderLayout.CENTER);
0365: }
0366:
0367: /**
0368: *
0369: *
0370: */
0371: private void initializeTranscriptTemplate() {
0372: // Initialize the chat transcript template
0373: try {
0374: // Read the template file into memory. Note, it doesn't seem to
0375: // work to get the resource as a URL and call setPage() on the
0376: // editor pane.
0377: InputStream is = ChatComponent.class
0378: .getResourceAsStream(CHAT_TEMPLATE_RESOURCE);
0379: BufferedReader reader = new BufferedReader(
0380: new InputStreamReader(is));
0381:
0382: StringBuffer buffer = new StringBuffer();
0383: String line = null;
0384:
0385: while ((line = reader.readLine()) != null)
0386: buffer.append(line).append("\n");
0387:
0388: // Determine the current font size in the IDE
0389: int baseFontSize = Math.max(new JLabel().getFont()
0390: .getSize(), 12);
0391: int smallFontSize = baseFontSize - 1;
0392:
0393: // Replace global tokens
0394: String templateContent = buffer.toString();
0395: templateContent = StringTokenizer2.replace(templateContent,
0396: TOKEN_BASE_FONT_SIZE, "" + baseFontSize);
0397: templateContent = StringTokenizer2.replace(templateContent,
0398: TOKEN_SMALL_FONT_SIZE, "" + smallFontSize);
0399:
0400: // Set the template content
0401: transcriptPane.setText(templateContent);
0402:
0403: // Find and cache the element at which we will insert additional
0404: // elements
0405: HTMLDocument document = (HTMLDocument) transcriptPane
0406: .getDocument();
0407: endElement = document.getElement(END_ELEMENT_ID);
0408:
0409: // Get the message template HTML from the document itself
0410: Element templateParent = document
0411: .getElement(MESSAGE_TEMPLATE_ELEMENT_ID);
0412:
0413: // Switch to the implied <p> inside the <div>
0414: templateParent = templateParent.getElement(0);
0415:
0416: // Find the first comment element of the template parent element.
0417: // This will be our template string.
0418: for (int i = 0; i < templateParent.getElementCount(); i++) {
0419: Element element = templateParent.getElement(i);
0420:
0421: if (element.getAttributes().isDefined(
0422: HTML.Attribute.COMMENT)) {
0423: messageTemplate = (String) element.getAttributes()
0424: .getAttribute(HTML.Attribute.COMMENT);
0425:
0426: break;
0427: }
0428: }
0429:
0430: // Get the system message template
0431: Element systemMsgTemplateParent = document
0432: .getElement(SYSTEM_MESSAGE_TEMPLATE_ELEMENT_ID);
0433:
0434: // Switch to the implied <p> inside the <div>
0435: systemMsgTemplateParent = systemMsgTemplateParent
0436: .getElement(0);
0437:
0438: // Find the first comment element of the template parent element.
0439: // This will be our template string.
0440: for (int i = 0; i < systemMsgTemplateParent
0441: .getElementCount(); i++) {
0442: Element element = systemMsgTemplateParent.getElement(i);
0443:
0444: if (element.getAttributes().isDefined(
0445: HTML.Attribute.COMMENT)) {
0446: systemMessageTemplate = (String) element
0447: .getAttributes().getAttribute(
0448: HTML.Attribute.COMMENT);
0449:
0450: break;
0451: }
0452: }
0453:
0454: // Get the chat message template
0455: Element chatMessageTemplateParent = document
0456: .getElement(CHAT_MESSAGE_TEMPLATE_ELEMENT_ID);
0457:
0458: // Switch to the implied <p> inside the <div>
0459: chatMessageTemplateParent = chatMessageTemplateParent
0460: .getElement(0);
0461:
0462: // Find the first comment element of the template parent element.
0463: // This will be our template string.
0464: for (int i = 0; i < chatMessageTemplateParent
0465: .getElementCount(); i++) {
0466: Element element = chatMessageTemplateParent
0467: .getElement(i);
0468:
0469: if (element.getAttributes().isDefined(
0470: HTML.Attribute.COMMENT)) {
0471: chatMessageTemplate = (String) element
0472: .getAttributes().getAttribute(
0473: HTML.Attribute.COMMENT);
0474:
0475: break;
0476: }
0477: }
0478: } catch (IOException e) {
0479: // Shouldn't happen
0480: Debug.errorManager.notify(e);
0481: }
0482: }
0483:
0484: /**
0485: *
0486: *
0487: */
0488: private void initTypingTimer() {
0489: TypingTimerActionListener actionListener = new TypingTimerActionListener();
0490: typingTimer = new javax.swing.Timer(TYPE_TIMER_INTERVAL,
0491: actionListener);
0492: typingTimer.setRepeats(false);
0493: }
0494:
0495: /**
0496: * Code to dynamically add the buttons by using the lookup api of the netbeans
0497: *
0498: */
0499: private JToolBar initializeToolbar() {
0500: JToolBar inputToolbar = new JToolBar(JToolBar.HORIZONTAL);
0501: inputToolbar.setFloatable(false);
0502: inputToolbar.setBorder(new CompoundBorder(new EmptyBorder(3, 0,
0503: 0, 0), inputToolbar.getBorder()));
0504:
0505: ContentTypeActionListener actionListener = new ContentTypeActionListener();
0506:
0507: ButtonGroup group = new ButtonGroup();
0508:
0509: Lookup.Result result = Lookup.getDefault().lookup(
0510: new Lookup.Template(CollabContentType.class));
0511: Collection messageTypeCollection = result.allInstances();
0512:
0513: int i = 1;
0514: for (Iterator it = messageTypeCollection.iterator(); it
0515: .hasNext(); i++) {
0516: CollabContentType messageType = (CollabContentType) it
0517: .next();
0518: Image icon = messageType.getIcon();
0519: String displayName = messageType.getDisplayName();
0520: String contentType = messageType.getContentType();
0521: JToggleButton button = new JToggleButton(
0522: new ImageIcon(icon));
0523: button.putClientProperty("contentType", contentType); //NOI18N
0524:
0525: if (i < 9)
0526: button.setMnemonic((char) ('0' + i));
0527: button.setToolTipText(displayName);
0528: if (messageType.getContentType().equals(
0529: ContentTypes.UNKNOWN_TEXT)) {
0530: chatFocusButton = button;
0531: }
0532: button.addActionListener(actionListener);
0533:
0534: group.add(button);
0535: inputToolbar.add(button);
0536: contentTypeToButton.put(contentType, button);
0537: }
0538:
0539: inputToolbar.addSeparator();
0540:
0541: Image smileyButtonIcon = org.openide.util.Utilities
0542: .loadImage("org/netbeans/modules/collab/channel/chat/"
0543: + "resources/emoticons/emo_smiley16.png"); // NOI18N
0544: smileyButton = new JButton(new ImageIcon(smileyButtonIcon));
0545: smileyButton.setMnemonic(NbBundle.getMessage(
0546: ChatComponent.class,
0547: "MNE_ChatComponent_MIMEType_Smileys").charAt(0));
0548: smileyButton.setToolTipText(NbBundle.getMessage(
0549: ChatComponent.class,
0550: "LBL_ChatComponent_MIMEType_Smileys"));
0551: smileyButton.setRequestFocusEnabled(false);
0552: smileyButton.addActionListener(new SmileListener());
0553: inputToolbar.add(smileyButton);
0554:
0555: return inputToolbar;
0556: }
0557:
0558: /*author Smitha Krishna Nagesh*/
0559: public JPopupMenu createSmileMenu() {
0560: newMenu = new JPopupMenu();
0561: newMenu.setLayout(new GridLayout(3, 2));
0562: newMenu.setPopupSize(50, 50);
0563: ButtonClickedListener listener = new ButtonClickedListener();
0564:
0565: newMenu.add(createSmileButton(":-)", "emo_smiley", "Smile",
0566: listener));
0567: newMenu.add(createSmileButton(":-(", "emo_sad", "Frown",
0568: listener));
0569: newMenu.add(createSmileButton(";-)", "emo_wink", "Wink",
0570: listener));
0571: newMenu.add(createSmileButton(":=)", "emo_laughing", "Laugh",
0572: listener));
0573: newMenu.add(createSmileButton("8-)", "emo_cool", "Cool",
0574: listener));
0575: newMenu.add(createSmileButton(":-D", "emo_grin", "Grin",
0576: listener));
0577:
0578: newMenu.pack();
0579:
0580: return newMenu;
0581: }
0582:
0583: private JButton createSmileButton(String smile, String icon,
0584: String key, ActionListener listener) {
0585: Image img = org.openide.util.Utilities
0586: .loadImage("org/netbeans/modules/collab/channel/chat/resources/emoticons/"
0587: + icon + "16.png"); // NOI18N
0588: JButton button = new JButton(new ImageIcon(img));
0589: button.setActionCommand(smile.concat(" "));
0590: String tip = NbBundle.getMessage(ChatComponent.class,
0591: "LBL_Emoticon_" + key);
0592: button.setToolTipText(tip);
0593: button.setMnemonic(tip.charAt(0));
0594: button.addActionListener(listener);
0595:
0596: button.setContentAreaFilled(false);
0597: button.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
0598: button.setBorderPainted(false);
0599: button.setRequestFocusEnabled(false);
0600: return button;
0601: }
0602:
0603: ////////////////////////////////////////////////////////////////////////////
0604: // Accessors
0605: ////////////////////////////////////////////////////////////////////////////
0606:
0607: /**
0608: *
0609: *
0610: */
0611: public ChatCollablet getChannel() {
0612: return channel;
0613: }
0614:
0615: /**
0616: *
0617: *
0618: */
0619: public JEditorPane getTranscriptPane() {
0620: return transcriptPane;
0621: }
0622:
0623: /**
0624: *
0625: *
0626: */
0627: public ChatInputPane getInputPane() {
0628: return inputPane;
0629: }
0630:
0631: /**
0632: *
0633: *
0634: */
0635: public JButton getSendButton() {
0636: return sendButton;
0637: }
0638:
0639: ////////////////////////////////////////////////////////////////////////////
0640: // Event notifications
0641: ////////////////////////////////////////////////////////////////////////////
0642:
0643: /**
0644: *
0645: *
0646: */
0647: public void addNotify() {
0648: super .addNotify();
0649: SwingUtilities.invokeLater(new Runnable() {
0650: public void run() {
0651: if (getInputPane() != null) {
0652: getInputPane().requestFocus();
0653: }
0654: }
0655: });
0656: }
0657:
0658: ////////////////////////////////////////////////////////////////////////////
0659: // Input support methods
0660: ////////////////////////////////////////////////////////////////////////////
0661:
0662: /**
0663: *
0664: *
0665: */
0666: public String getInputContentType() {
0667: return inputContentType;
0668: }
0669:
0670: /**
0671: *
0672: *
0673: */
0674: public void setInputContentType(String contentType, boolean syncUI,
0675: boolean autoselected) {
0676: if (contentType == null) {
0677: throw new IllegalArgumentException(
0678: "contentType cannot be null");
0679: }
0680:
0681: // Remember what the content type was if this is happening due to
0682: // auto-selection during a paste. Always remember the first content
0683: // type that user had manually selected.
0684: if (autoselected) {
0685: if (previouslySelectedContentType == null) {
0686: previouslySelectedContentType = inputContentType;
0687: }
0688: }
0689:
0690: // else
0691: // {
0692: // previouslySelectedContentType=null;
0693: // }
0694: inputContentType = contentType;
0695:
0696: // Sync the input pane; remember the text and the selection
0697: String text = getInputPane().getText();
0698: int selStart = getInputPane().getSelectionStart();
0699: int selEnd = getInputPane().getSelectionEnd();
0700: int caretPos = getInputPane().getCaretPosition();
0701:
0702: getInputPane()._setContentType(contentType);
0703: getInputPane().popUpMenu();
0704: getInputPane().setText(text);
0705: getInputPane().setCaretPosition(caretPos);
0706: getInputPane().setSelectionStart(selStart);
0707: getInputPane().setSelectionEnd(selEnd);
0708: getInputPane().requestFocus();
0709:
0710: if (syncUI) {
0711: SwingUtilities.invokeLater(new ContentTypeSynchronizer());
0712: }
0713: }
0714:
0715: ////////////////////////////////////////////////////////////////////////////
0716: // Message management
0717: ////////////////////////////////////////////////////////////////////////////
0718:
0719: /**
0720: *
0721: *
0722: */
0723: protected void sendInput() {
0724: // Send a message to the conference
0725: try {
0726: CollabMessage message = getChannel().getConversation()
0727: .createMessage();
0728:
0729: // Set the message content
0730: String content = getInputPane().getText();
0731: message.setContent(content);
0732:
0733: // Set the actual content type to a different, chat-specific header.
0734: // The message content type will always be text/plain
0735: message.setHeader(
0736: ChatCollablet.DISPLAY_CONTENT_TYPE_HEADER,
0737: getInputContentType());
0738:
0739: // TODO: Tag the message as belonging to the chat channel
0740: // Is this necessary if we are sending SOAP?
0741: message.setHeader("x-channel", "chat"); // NOI18N
0742:
0743: // Send the message
0744: getChannel().getConversation().sendMessage(message);
0745: } catch (CollabException e) {
0746: Debug.errorManager.notify(e);
0747: }
0748:
0749: // Clear the message text
0750: getInputPane().setText("");
0751: getInputPane().requestFocus();
0752:
0753: // Restore the content type if auto-selection occured
0754: if (previouslySelectedContentType != null) {
0755: setInputContentType(previouslySelectedContentType, true,
0756: false);
0757: previouslySelectedContentType = null;
0758: }
0759: }
0760:
0761: private String replaceAll(String origStr, String targetStr,
0762: String replacedStr) {
0763: StringBuffer sb = new StringBuffer(origStr);
0764: int idx;
0765: while ((idx = sb.indexOf(targetStr)) != -1) {
0766: sb.replace(idx, idx + targetStr.length(), replacedStr);
0767: }
0768: return sb.toString();
0769: }
0770:
0771: private boolean strContains(String str, String pattern) {
0772: return str.indexOf(pattern) != -1;
0773: }
0774:
0775: /**
0776: *
0777: *
0778: */
0779: public void appendMessage(CollabMessage message) {
0780: try {
0781: // Convert message to HTML
0782: String html = convertToHtml(message);
0783:
0784: // fixme: pattern ") is first expanded to ") which will then find a smile there.
0785: // Replace smiles only in plain text
0786: String contentType = message
0787: .getHeader(ChatCollablet.DISPLAY_CONTENT_TYPE_HEADER);
0788: if ((contentType == null)
0789: || contentType.equals(ContentTypes.UNKNOWN_TEXT)) {
0790: for (int i = 0; i < smiles.length; i++) {
0791: if (strContains(html, smiles[i][0])) {
0792: URL smileUrl = ChatComponent.class
0793: .getResource("resources/emoticons/"
0794: + smiles[i][1]);
0795: String tag = "<img align=\"center\" src="
0796: + smileUrl + "></img>";
0797: html = replaceAll(html, smiles[i][0], tag);
0798: }
0799: }
0800: }
0801:
0802: // Insert the message text before the start of the epilogue
0803: HTMLDocument document = (HTMLDocument) getTranscriptPane()
0804: .getDocument();
0805: document.insertBeforeStart(getEndElement(), html);
0806:
0807: //Debug.out.println("Appended:\n"+html);
0808: // Scroll to bottom of document
0809: SwingUtilities.invokeLater(new Runnable() {
0810: public void run() {
0811: scrollToLastMessage();
0812: }
0813: });
0814: } catch (Exception e) {
0815: // TODO: Do something appropriate here
0816: Debug.errorManager.notify(e);
0817: }
0818: }
0819:
0820: /**
0821: *
0822: *
0823: */
0824: public void appendSystemMessage(String message) {
0825: try {
0826: String content = SyntaxColoring.convertToHTML(message,
0827: ContentTypes.TEXT);
0828:
0829: String html = getSystemMessageTemplate();
0830: html = StringTokenizer2.replace(html, TOKEN_MESSAGE_CLASS,
0831: MESSAGE_CLASS_SYSTEM);
0832: html = StringTokenizer2.replace(html, TOKEN_TIMESTAMP,
0833: SimpleDateFormat.getTimeInstance().format(
0834: new Date()));
0835: html = StringTokenizer2.replace(html, TOKEN_MESSAGE,
0836: content);
0837:
0838: // Insert the message text before the start of the
0839: // epilogue
0840: HTMLDocument document = (HTMLDocument) getTranscriptPane()
0841: .getDocument();
0842: document.insertBeforeStart(getEndElement(), html);
0843:
0844: // Scroll to bottom of document
0845: SwingUtilities.invokeLater(new Runnable() {
0846: public void run() {
0847: scrollToLastMessage();
0848: }
0849: });
0850: } catch (Exception e) {
0851: // TODO: Do something appropriate here
0852: Debug.errorManager.notify(e);
0853: }
0854: }
0855:
0856: /**
0857: *
0858: *
0859: */
0860: public void scrollToLastMessage() {
0861: try {
0862: // Find the parent of the end element
0863: Element parentElement = getEndElement().getParentElement();
0864:
0865: // Figure out the last element in the parent container (besides
0866: // the end element)
0867: Element lastElement = parentElement
0868: .getElement(parentElement.getElementCount() - 2);
0869:
0870: // Get the element rectangle
0871: Rectangle rect = getTranscriptPane().modelToView(
0872: lastElement.getStartOffset());
0873:
0874: if (rect != null) {
0875: // Make the height of the element rectangle the same as
0876: // the visible size of the component
0877: Rectangle visRect = getTranscriptPane()
0878: .getVisibleRect();
0879: rect.height = visRect.height;
0880:
0881: // Scroll to the last element
0882: getTranscriptPane().scrollRectToVisible(rect);
0883: }
0884: } catch (Exception e) {
0885: Debug.debugNotify(e);
0886: }
0887: }
0888:
0889: ////////////////////////////////////////////////////////////////////////////
0890: // Formatting methods
0891: ////////////////////////////////////////////////////////////////////////////
0892:
0893: /**
0894: * Returns the element before which new messages should be inserted
0895: *
0896: */
0897: protected Element getEndElement() {
0898: return endElement;
0899: }
0900:
0901: /**
0902: * Returns the message template string
0903: *
0904: */
0905: protected String getMessageTemplate() {
0906: return messageTemplate;
0907: }
0908:
0909: /**
0910: * Returns the message template string
0911: *
0912: */
0913: protected String getChatMessageTemplate() {
0914: return chatMessageTemplate;
0915: }
0916:
0917: /**
0918: * Returns the system message template string
0919: *
0920: */
0921: protected String getSystemMessageTemplate() {
0922: return systemMessageTemplate;
0923: }
0924:
0925: /**
0926: *
0927: *
0928: */
0929: private Map getSenderStyles() {
0930: return senderStyles;
0931: }
0932:
0933: /**
0934: *
0935: *
0936: */
0937: protected String convertToHtml(CollabMessage message) {
0938: String content = null;
0939: String plainContent = null;
0940: String messageClass = null;
0941:
0942: String contentType = message
0943: .getHeader(ChatCollablet.DISPLAY_CONTENT_TYPE_HEADER);
0944:
0945: try {
0946: content = message.getContent();
0947: assert content != null : "Content was null";
0948: plainContent = content;
0949:
0950: if ((contentType == null)
0951: || contentType.equals(ContentTypes.UNKNOWN_TEXT)) {
0952: // Trim the content, with the assumption it is coming from
0953: // a plain IM client
0954: content = SyntaxColoring.convertToHTML(content.trim(),
0955: ContentTypes.UNKNOWN_TEXT);
0956: messageClass = MESSAGE_CLASS_CHAT;
0957: contentType = ContentTypes.UNKNOWN_TEXT;
0958:
0959: // Escape any hyperlinks
0960: content = replaceHyperlinks(content);
0961: } else if (contentType.equals(ContentTypes.HTML)) {
0962: content = SyntaxColoring.convertToHTML(content,
0963: ContentTypes.HTML);
0964: messageClass = MESSAGE_CLASS_HTML;
0965:
0966: // Escape any hyperlinks
0967: content = replaceHyperlinks(content);
0968: } else if (contentType.equals(ContentTypes.XML)) {
0969: content = SyntaxColoring.convertToHTML(content,
0970: ContentTypes.XML);
0971: messageClass = MESSAGE_CLASS_XML;
0972:
0973: // Escape any hyperlinks
0974: content = replaceHyperlinks(content);
0975: } else if (contentType.equals(ContentTypes.JAVA)) {
0976: content = SyntaxColoring.convertToHTML(content,
0977: ContentTypes.JAVA);
0978: messageClass = MESSAGE_CLASS_JAVA;
0979: } else {
0980: content = SyntaxColoring.convertToHTML(content,
0981: ContentTypes.TEXT);
0982: messageClass = MESSAGE_CLASS_TEXT;
0983:
0984: // Escape any hyperlinks
0985: content = replaceHyperlinks(content);
0986: }
0987: } catch (CollabException e) {
0988: // TODO: Proper error handling here
0989: content = e.getMessage();
0990: Debug.debugNotify(e);
0991: }
0992:
0993: // Determine the style for the sender, based on whether we've seen
0994: // this sender before or not
0995: String sender = message.getOriginator().getDisplayName();
0996: String senderClass = DEFAULT_SENDER_CLASS;
0997:
0998: if (getSenderStyles().containsKey(sender)) {
0999: senderClass = (String) getSenderStyles().get(sender);
1000: } else {
1001: // Allocate a new sender style. Styles for senders are embedded
1002: // in the document and referenced numerically from here.
1003: if ((++lastSenderStyleIndex) <= MAX_SENDER_CLASSES) {
1004: senderClass = allocateNewSenderStyleClass(sender);
1005: } else {
1006: // Do nothing, leave as default
1007: }
1008: }
1009:
1010: // Fill in the message template. Use the chat template if the content
1011: // type is simple/unknown text.
1012: String result = (contentType == ContentTypes.UNKNOWN_TEXT) ? getChatMessageTemplate()
1013: : getMessageTemplate();
1014:
1015: // String result=getMessageTemplate();
1016: result = StringTokenizer2.replace(result, TOKEN_SENDER_CLASS,
1017: senderClass);
1018: result = StringTokenizer2.replace(result, TOKEN_MESSAGE_CLASS,
1019: messageClass);
1020: result = StringTokenizer2.replace(result, TOKEN_SENDER, sender);
1021: result = StringTokenizer2.replace(result, TOKEN_MESSAGE,
1022: content);
1023: result = StringTokenizer2.replace(result, TOKEN_MESSAGE_TEXT,
1024: plainContent);
1025: result = StringTokenizer2.replace(result, TOKEN_TIMESTAMP,
1026: SimpleDateFormat.getTimeInstance().format(new Date()));
1027: result = StringTokenizer2.replace(result, TOKEN_CONTENT_TYPE,
1028: contentType);
1029:
1030: return result;
1031: }
1032:
1033: /**
1034: *
1035: *
1036: */
1037: private String replaceHyperlinks(String content) {
1038: String originalContent = content;
1039:
1040: try {
1041: int index = content.indexOf("http://"); // NOI18N
1042:
1043: while (index != -1) {
1044: int startIndex = index;
1045: int endIndex = startIndex;
1046:
1047: // Find the end of the URL
1048: LOOP: for (; endIndex < content.length(); endIndex++) {
1049: switch (content.charAt(endIndex)) {
1050: case ',':
1051: case '\'':
1052: case '!':
1053: case ' ':
1054: case '\r':
1055: case '\n':
1056: case '\t':
1057: break LOOP;
1058:
1059: case '&': // Check for entities
1060:
1061: if (content.startsWith(""", endIndex) || // NOI18N
1062: content.startsWith("<", endIndex) || // NOI18N
1063: content.startsWith(">", endIndex) || // NOI18N
1064: content.startsWith(" ", endIndex)) // NOI18N
1065: {
1066: break LOOP;
1067: }
1068:
1069: default:
1070:
1071: continue;
1072: }
1073: }
1074:
1075: String link = content.substring(startIndex, endIndex);
1076:
1077: // Strip any trailing periods
1078: if (link.lastIndexOf(".") == (link.length() - 1)) // NOI18N
1079: {
1080: link = link.substring(0, link.length() - 1);
1081: endIndex--;
1082: }
1083:
1084: try {
1085: java.net.URL url = new java.net.URL(link);
1086:
1087: // If we got here, the link is valid
1088: link = "<a href='" + link + "'>" + link + "</a>"; // NOI18N
1089: index += link.length();
1090:
1091: // Replace the link with the HTML markup
1092: StringBuffer buffer = new StringBuffer(content);
1093: buffer.replace(startIndex, endIndex, link);
1094: content = buffer.toString();
1095: } catch (Exception e) {
1096: // Ignore this link
1097: }
1098:
1099: index = content.indexOf("http://", index); // NOI18N
1100: }
1101:
1102: return content;
1103: } catch (Exception e) {
1104: Debug.debugNotify(e);
1105:
1106: return originalContent;
1107: }
1108: }
1109:
1110: /**
1111: *
1112: *
1113: */
1114: private String allocateNewSenderStyleClass(String sender) {
1115: String senderClass = "sender" + lastSenderStyleIndex; // NOI18N
1116: getSenderStyles().put(sender, senderClass);
1117:
1118: return senderClass;
1119: }
1120:
1121: /**
1122: * send user typing status to participants
1123: *
1124: */
1125: private void sendTypingStatus(boolean typing) {
1126: try {
1127: CollabMessage message = getChannel().getConversation()
1128: .createMessage();
1129:
1130: int status = typing ? CollabMessage.TYPING_ON
1131: : CollabMessage.TYPING_OFF;
1132: message.sendStatus(status);
1133: } catch (CollabException e) {
1134: Debug.errorManager.notify(e);
1135: }
1136: }
1137:
1138: /**
1139: *
1140: *
1141: */
1142: public void updateStatusMessage(CollabPrincipal principal,
1143: boolean typing) {
1144: // Effectively, we manage a reference count for the set of principals
1145: // currently typing
1146: if (typing) {
1147: if (!typingParticipants.contains(principal)) {
1148: typingParticipants.add(principal);
1149: }
1150: } else {
1151: typingParticipants.remove(principal);
1152: }
1153:
1154: // Determine what text to show
1155: String text = "";
1156:
1157: if (typingParticipants.size() == 1) {
1158: // Single user, show their name
1159: CollabPrincipal participant = (CollabPrincipal) typingParticipants
1160: .iterator().next();
1161: text = " "
1162: + NbBundle.getMessage(ChatComponent.class,
1163: "LBL_ChatComponent_USER_TYPING", // NOI18N
1164: participant.getDisplayName());
1165: } else if (typingParticipants.size() > 1) {
1166: // Multiple users, show general message
1167: text = " "
1168: + NbBundle.getMessage(ChatComponent.class,
1169: "LBL_ChatComponent_SERVERAL_USERS_TYPING"); // NOI18N
1170: }
1171:
1172: typingMessageLabel.setText(text);
1173: }
1174:
1175: /**
1176: *
1177: *
1178: */
1179: private javax.swing.Timer getTypingTimer() {
1180: return typingTimer;
1181: }
1182:
1183: ////////////////////////////////////////////////////////////////////////////
1184: // Inner class
1185: ////////////////////////////////////////////////////////////////////////////
1186:
1187: /**
1188: *
1189: *
1190: */
1191: public void hyperlinkUpdate(HyperlinkEvent event) {
1192: if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
1193: try {
1194: java.net.URL url = event.getURL();
1195: HtmlBrowser.URLDisplayer.getDefault().showURL(url);
1196: } catch (Exception e) {
1197: // Ignore
1198: Debug.debugNotify(e);
1199: }
1200: }
1201: }
1202:
1203: /*Creates the popup menu
1204: *author Smitha Krishna Nagesh
1205: */
1206: private void createPopupMenu() {
1207: JMenuItem menuItem;
1208: popupMenu = new JPopupMenu();
1209:
1210: menuItem = new JMenuItem(new DefaultEditorKit.CopyAction());
1211: menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C,
1212: KeyEvent.CTRL_DOWN_MASK, false));
1213: menuItem.setText("Copy");
1214: popupMenu.add(menuItem);
1215: popupMenu.addSeparator();
1216:
1217: menuItem = new JMenuItem("Save");
1218: popupMenu.add(menuItem);
1219: menuItem.addActionListener(new FileChooserActionListener());
1220: menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
1221: KeyEvent.CTRL_DOWN_MASK, false));
1222:
1223: MouseListener popupListener = new PopupListener();
1224: transcriptPane.addMouseListener(popupListener);
1225: }
1226:
1227: /*author Smitha Krishna Nagesh*/
1228: protected class SmileListener implements ActionListener {
1229: public void actionPerformed(ActionEvent event) {
1230: newMenu.show(smileyButton, 10, 10);
1231: inputPane.grabFocus();
1232: }
1233: }
1234:
1235: /*author Smitha Krishna Nagesh*/
1236: protected class ButtonClickedListener implements ActionListener {
1237: public void actionPerformed(ActionEvent menuEvent) {
1238: JButton button = (JButton) menuEvent.getSource();
1239: String str = button.getActionCommand();
1240:
1241: try {
1242: int caretPos = inputPane.getCaretPosition();
1243: inputPane.getDocument().insertString(caretPos, str,
1244: null);
1245: inputPane.setCaretPosition(caretPos + str.length());
1246: newMenu.setVisible(false);
1247: } catch (Exception e) {
1248: ErrorManager.getDefault().notify(e);
1249: }
1250: }
1251: }
1252:
1253: ////////////////////////////////////////////////////////////////////////////
1254: // Inner class
1255: ////////////////////////////////////////////////////////////////////////////
1256:
1257: /**
1258: *
1259: *
1260: */
1261: protected class ChatHTMLFactory extends HTMLEditorKit.HTMLFactory {
1262: /**
1263: *
1264: *
1265: */
1266: public View create(Element element) {
1267: // Unfortunately, the Swing HTML parser doesn't respect the
1268: // display: none style, so we no-op the rendering of the message
1269: // template element here.
1270: if (element.getAttributes().isDefined(HTML.Attribute.ID)
1271: && element.getAttributes().getAttribute(
1272: HTML.Attribute.ID).equals(
1273: MESSAGE_TEMPLATE_ELEMENT_ID)) {
1274: return new BlockView(element, View.Y_AXIS) {
1275: public void paint(Graphics g, Shape allocation) {
1276: // Do nothing
1277: }
1278: };
1279: } else {
1280: return super .create(element);
1281: }
1282: }
1283: }
1284:
1285: ////////////////////////////////////////////////////////////////////////////
1286: // Inner class
1287: ////////////////////////////////////////////////////////////////////////////
1288:
1289: /**
1290: *
1291: *
1292: */
1293: protected class ConversationChangeListener extends Object implements
1294: PropertyChangeListener {
1295: /**
1296: *
1297: *
1298: */
1299: public void propertyChange(PropertyChangeEvent event) {
1300: if (event.getPropertyName().equals(
1301: ChatCollablet.PROP_TRANSCRIPT)) {
1302: try {
1303: CollabMessage message = (CollabMessage) event
1304: .getNewValue();
1305: appendMessage(message);
1306: CollabManager
1307: .getDefault()
1308: .getUserInterface()
1309: .notifyConversationEvent(
1310: ChatComponent.this .getChannel()
1311: .getConversation(),
1312: UserInterface.NOTIFY_CHAT_MESSAGE_RECEIVED);
1313: } catch (Exception e) {
1314: // TODO: Do something appropriate here
1315: Debug.errorManager.notify(e);
1316: }
1317: } else if (event.getPropertyName().equals(
1318: Conversation.PROP_PARTICIPANTS)) {
1319: boolean joined = event.getNewValue() != null;
1320: CollabPrincipal principal = joined ? (CollabPrincipal) event
1321: .getNewValue()
1322: : (CollabPrincipal) event.getOldValue();
1323:
1324: try {
1325: String key = joined ? "MSG_ChatComponent_UserJoined" // NOI18N
1326: : "MSG_ChatComponent_UserLeft"; // NOI18N
1327:
1328: String message = NbBundle.getMessage(
1329: ChatComponent.class, key, principal
1330: .getDisplayName(), SimpleDateFormat
1331: .getTimeInstance().format(
1332: new Date()));
1333: appendSystemMessage(message);
1334: } catch (Exception e) {
1335: // TODO: Do something appropriate here
1336: Debug.errorManager.notify(e);
1337: }
1338: } else if (event.getPropertyName().equals(
1339: Conversation.USER_TYPING_ON)) {
1340: CollabPrincipal principal = (CollabPrincipal) event
1341: .getNewValue();
1342: updateStatusMessage(principal, true);
1343: } else if (event.getPropertyName().equals(
1344: Conversation.USER_TYPING_OFF)) {
1345: CollabPrincipal principal = (CollabPrincipal) event
1346: .getNewValue();
1347: updateStatusMessage(principal, false);
1348: }
1349: }
1350: }
1351:
1352: ////////////////////////////////////////////////////////////////////////////
1353: // Inner class
1354: ////////////////////////////////////////////////////////////////////////////
1355:
1356: /**
1357: *
1358: *
1359: */
1360: private class ContentTypeActionListener extends Object implements
1361: ActionListener {
1362: public void actionPerformed(ActionEvent event) {
1363: // This will be invoked only when the user selects the button.
1364: // If the user explicitly selects a button, cancel the previous
1365: // selection
1366: JToggleButton source = (JToggleButton) event.getSource();
1367: String type = (String) source
1368: .getClientProperty("contentType"); //NOI18N
1369: smileyButton.setEnabled(type
1370: .equals(ContentTypes.UNKNOWN_TEXT));
1371:
1372: previouslySelectedContentType = null;
1373:
1374: setInputContentType(type, false, false);
1375: }
1376: }
1377:
1378: ////////////////////////////////////////////////////////////////////////////
1379: // Inner class
1380: ////////////////////////////////////////////////////////////////////////////
1381:
1382: /**
1383: * This part of the code has been changed inorder to suit the needs of the dynamic addition of the button
1384: *
1385: */
1386: protected class ContentTypeSynchronizer extends Object implements
1387: Runnable {
1388: /**
1389: *
1390: *
1391: */
1392: public void run() {
1393: JToggleButton button = (JToggleButton) contentTypeToButton
1394: .get(inputContentType);
1395: button.setSelected(true);
1396: }
1397: }
1398:
1399: ////////////////////////////////////////////////////////////////////////////
1400: // Inner class
1401: ////////////////////////////////////////////////////////////////////////////
1402:
1403: /**
1404: *
1405: *
1406: */
1407: protected class TypingTimerActionListener extends AbstractAction
1408: implements ActionListener {
1409: /**
1410: *
1411: *
1412: */
1413: public void actionPerformed(ActionEvent event) {
1414: getTypingTimer().stop();
1415: sendTypingStatus(false);
1416: }
1417: }
1418:
1419: ////////////////////////////////////////////////////////////////////////////
1420: // Inner class
1421: ////////////////////////////////////////////////////////////////////////////
1422:
1423: /**
1424: *
1425: *
1426: */
1427: protected class InputPaneDocumentListener implements
1428: DocumentListener, PropertyChangeListener {
1429: private Document oldDoc;
1430:
1431: public InputPaneDocumentListener() {
1432: getInputPane().addPropertyChangeListener(this );
1433: updateDocument();
1434: }
1435:
1436: public void updateTimer() {
1437: if (getTypingTimer() != null) {
1438: if (getTypingTimer().isRunning()) {
1439: getTypingTimer().restart();
1440: } else {
1441: if (getInputPane().getDocument().getLength() > 0) {
1442: getTypingTimer().start();
1443: sendTypingStatus(true);
1444: }
1445: }
1446: }
1447: }
1448:
1449: public void changedUpdate(DocumentEvent e) {
1450: updateTimer();
1451: }
1452:
1453: public void insertUpdate(DocumentEvent e) {
1454: updateTimer();
1455: }
1456:
1457: public void removeUpdate(DocumentEvent e) {
1458: updateTimer();
1459: }
1460:
1461: public void propertyChange(PropertyChangeEvent evt) {
1462: if (evt.getPropertyName().equals("document")) {
1463: updateDocument();
1464: }
1465: }
1466:
1467: private void updateDocument() {
1468: if (oldDoc != null)
1469: oldDoc.removeDocumentListener(this );
1470: oldDoc = getInputPane().getDocument();
1471: oldDoc.addDocumentListener(this );
1472: }
1473: }
1474:
1475: ////////////////////////////////////////////////////////////////////////////
1476: // Inner class
1477: ////////////////////////////////////////////////////////////////////////////
1478:
1479: /**
1480: *
1481: *
1482: */
1483: protected class SendButtonActionListener extends AbstractAction
1484: implements ActionListener {
1485: /**
1486: *
1487: *
1488: */
1489: public void actionPerformed(ActionEvent event) {
1490: getTypingTimer().stop();
1491: sendTypingStatus(false);
1492:
1493: sendInput();
1494: }
1495: }
1496:
1497: ////////////////////////////////////////////////////////////////////////////
1498: // Inner class
1499: ////////////////////////////////////////////////////////////////////////////
1500:
1501: /**
1502: * HACK: This class implements a proxy pattern in order to allow us to
1503: * "sniff" pasted content. Unfortunately, because TransferHandler has
1504: * protected methods, it requires us to use AccessibleObject to invoke
1505: * certain methods.
1506: *
1507: */
1508: public class InputPaneTransferHandler extends TransferHandler {
1509: private TransferHandler delegate;
1510:
1511: /**
1512: *
1513: *
1514: */
1515: public InputPaneTransferHandler(TransferHandler delegate) {
1516: super ();
1517: this .delegate = delegate;
1518: }
1519:
1520: /**
1521: *
1522: *
1523: */
1524: public boolean canImport(JComponent comp,
1525: DataFlavor[] transferFlavors) {
1526: return delegate.canImport(comp, transferFlavors);
1527: }
1528:
1529: /**
1530: *
1531: *
1532: */
1533: protected Transferable createTransferable(JComponent c) {
1534: Method method = null;
1535:
1536: try {
1537: method = getClass().getMethod("createTransferable", // NOI18N
1538: new Class[] { JComponent.class });
1539: method.setAccessible(true);
1540: } catch (Exception e) {
1541: Debug.debugNotify(e);
1542:
1543: // Cannot happen
1544: assert false : e.getMessage();
1545: }
1546:
1547: try {
1548: return (Transferable) method.invoke(delegate,
1549: new Object[] { c });
1550: } catch (Exception e) {
1551: // Shouldn't happen
1552: Debug.debugNotify(e);
1553: }
1554:
1555: // Fallback, just in case
1556: return super .createTransferable(c);
1557: }
1558:
1559: /**
1560: *
1561: *
1562: */
1563: public void exportAsDrag(JComponent comp, InputEvent e,
1564: int action) {
1565: delegate.exportAsDrag(comp, e, action);
1566: }
1567:
1568: /**
1569: *
1570: *
1571: */
1572: protected void exportDone(JComponent source, Transferable data,
1573: int action) {
1574: Method method = null;
1575:
1576: try {
1577: method = getClass().getMethod(
1578: "exportDone", // NOI18N
1579: new Class[] { JComponent.class,
1580: Transferable.class, Integer.TYPE });
1581: method.setAccessible(true);
1582: } catch (Exception e) {
1583: Debug.debugNotify(e);
1584:
1585: // Cannot happen
1586: assert false : e.getMessage();
1587: }
1588:
1589: try {
1590: method.invoke(delegate, new Object[] { source, data,
1591: new Integer(action) });
1592: } catch (Exception e) {
1593: // Shouldn't happen
1594: Debug.debugNotify(e);
1595:
1596: // Fallback, just in case
1597: super .exportDone(source, data, action);
1598: }
1599: }
1600:
1601: /**
1602: *
1603: *
1604: */
1605: public void exportToClipboard(JComponent comp, Clipboard clip,
1606: int action) {
1607: delegate.exportToClipboard(comp, clip, action);
1608: }
1609:
1610: /**
1611: *
1612: *
1613: */
1614: public int getSourceActions(JComponent c) {
1615: return delegate.getSourceActions(c);
1616: }
1617:
1618: /**
1619: *
1620: *
1621: */
1622: public Icon getVisualRepresentation(Transferable t) {
1623: return delegate.getVisualRepresentation(t);
1624: }
1625:
1626: /**
1627: *
1628: *
1629: */
1630: public boolean importData(JComponent component,
1631: Transferable transferable) {
1632: String content = getInputPane().getText();
1633:
1634: // Go ahead and import the data
1635: boolean result = delegate.importData(component,
1636: transferable);
1637:
1638: if (result
1639: && ((content == null) || (content.length() == 0))) {
1640: // Figure out what the best representation of the just-imported
1641: // data is, and set the current input content type accordingly
1642: DataFlavor[] flavors = transferable
1643: .getTransferDataFlavors();
1644: DataFlavor flavor = null;
1645:
1646: if ((flavor = containsContentType(flavors,
1647: ContentTypes.JAVA)) != null) {
1648: autoselectContentType(ContentTypes.JAVA);
1649: } else if ((flavor = containsContentType(flavors,
1650: ContentTypes.XML)) != null) {
1651: autoselectContentType(ContentTypes.XML);
1652: } else if ((flavor = containsContentType(flavors,
1653: ContentTypes.HTML)) != null) {
1654: // See if this is actually HTML or just plain text encoded
1655: // as HTML
1656: String htmlText = getContent(transferable, flavor);
1657: String plainText = getContent(transferable,
1658: containsContentType(flavors,
1659: ContentTypes.TEXT));
1660:
1661: if ((htmlText != null) && (plainText != null)) {
1662: if (htmlText.equals(plainText)) {
1663: // This is actually HTML
1664: autoselectContentType(ContentTypes.HTML);
1665: } else {
1666: // This is actually just plain text encoded as HTML
1667: setTextContentTypeConditionally();
1668: }
1669: } else {
1670: // If the HTML string was here, it's definitely HTML.
1671: // I don't know that this condition is actually ever
1672: // possible in any case; I think in all cases where
1673: // text/html is available, text/plain will also be
1674: // available.
1675: if (htmlText != null) {
1676: autoselectContentType(ContentTypes.HTML);
1677: } else {
1678: setTextContentTypeConditionally();
1679: }
1680: }
1681: } else if ((flavor = containsContentType(flavors,
1682: ContentTypes.TEXT)) != null) {
1683: setTextContentTypeConditionally();
1684: } else {
1685: // Do nothing
1686: }
1687: }
1688:
1689: return result;
1690: }
1691:
1692: /**
1693: *
1694: *
1695: */
1696: private String getContent(Transferable transferable,
1697: DataFlavor flavor) {
1698: if (flavor == null) {
1699: return null;
1700: }
1701:
1702: // Get the data so we can look at it
1703: Object data = null;
1704: String dataString = null;
1705:
1706: try {
1707: data = transferable.getTransferData(flavor);
1708: } catch (Exception e) {
1709: Debug.debugNotify(e);
1710: autoselectContentType(ContentTypes.HTML);
1711: }
1712:
1713: if (data != null) {
1714: // Convert to a String
1715: if (data instanceof String) {
1716: dataString = (String) data;
1717: } else if (data instanceof Reader) {
1718: try {
1719: BufferedReader reader = new BufferedReader(
1720: (Reader) data);
1721: StringBuffer buffer = new StringBuffer();
1722: String line = null;
1723:
1724: while ((line = reader.readLine()) != null)
1725: buffer.append(line).append("\n"); // NOI18N
1726:
1727: dataString = buffer.toString();
1728: } catch (Exception e) {
1729: Debug.debugNotify(e);
1730: }
1731: } else if (data instanceof InputStream) {
1732: try {
1733: String charset = flavor.getParameter("charset");
1734: BufferedReader reader = null;
1735:
1736: if (charset != null) {
1737: reader = new BufferedReader(
1738: new InputStreamReader(
1739: (InputStream) data, charset));
1740: } else {
1741: reader = new BufferedReader(
1742: new InputStreamReader(
1743: (InputStream) data));
1744: }
1745:
1746: StringBuffer buffer = new StringBuffer();
1747: String line = null;
1748:
1749: while ((line = reader.readLine()) != null)
1750: buffer.append(line).append("\n"); // NOI18N
1751:
1752: dataString = buffer.toString();
1753: } catch (Exception e) {
1754: Debug.debugNotify(e);
1755: }
1756: }
1757: }
1758:
1759: // We have to normalize line endings in order to compare strings
1760: // of different content types
1761: dataString = StringTokenizer2.replace(dataString, "\r\n",
1762: "\n");
1763:
1764: return dataString;
1765: }
1766:
1767: /**
1768: *
1769: *
1770: */
1771: private void setTextContentTypeConditionally() {
1772: // If it's not already text, set it to text. Don't
1773: // distinguish between formatted and plain text
1774: if (!getInputContentType()
1775: .equals(ContentTypes.UNKNOWN_TEXT)
1776: && !getInputContentType().equals(ContentTypes.TEXT)) {
1777: autoselectContentType(ContentTypes.TEXT);
1778: }
1779: }
1780:
1781: /**
1782: *
1783: *
1784: */
1785: private void autoselectContentType(String contentType) {
1786: setInputContentType(contentType, true, true);
1787: }
1788:
1789: /**
1790: *
1791: *
1792: */
1793: public DataFlavor containsContentType(DataFlavor[] flavors,
1794: String contentType) {
1795: // Debug.out.println("----------------------------");
1796: // for (int i=0; i<flavors.length; i++)
1797: // Debug.out.println(flavors[i].getMimeType());
1798: for (int i = 0; i < flavors.length; i++) {
1799: if (flavors[i].isMimeTypeEqual(contentType)) {
1800: return flavors[i];
1801: }
1802: }
1803:
1804: return null;
1805: }
1806: }
1807:
1808: ////////////////////////////////////////////////////////////////////////////
1809: // Inner class
1810: ////////////////////////////////////////////////////////////////////////////
1811:
1812: /* author Smitha Krishna Nagesh*/
1813: protected class PopupListener extends MouseAdapter {
1814: public void mousePressed(MouseEvent mouseEvt) {
1815: showPopup(mouseEvt);
1816: }
1817:
1818: public void mouseReleased(MouseEvent mouseEvt) {
1819: showPopup(mouseEvt);
1820: }
1821:
1822: private void showPopup(MouseEvent mouseEvt) {
1823: if (mouseEvt.isPopupTrigger()) {
1824: popupMenu.show(mouseEvt.getComponent(),
1825: mouseEvt.getX(), mouseEvt.getY());
1826: }
1827: }
1828: }
1829:
1830: ////////////////////////////////////////////////////////////////////////////
1831: // Inner class
1832: ////////////////////////////////////////////////////////////////////////////
1833:
1834: /* author Smitha Krishna Nagesh
1835: Pops up a FileChooser to choose a directory to save the file.*/
1836: protected class FileChooserActionListener implements ActionListener {
1837: JFileChooser chooseFile = new JFileChooser();
1838:
1839: public void actionPerformed(ActionEvent saveEvt) {
1840: chooseFile
1841: .setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
1842:
1843: int returnVal = chooseFile.showSaveDialog(null);
1844:
1845: if (returnVal == JFileChooser.APPROVE_OPTION) {
1846: File file = chooseFile.getSelectedFile();
1847:
1848: if (file.exists()) {
1849: int option = JOptionPane.showOptionDialog(
1850: null,
1851: NbBundle.getMessage(ChatComponent.class,
1852: "MSG_ChatComponent_FileChoosing"), //NOI18N
1853: NbBundle.getMessage(ChatComponent.class,
1854: "TITLE_Confirmation"), //NOI18N
1855: JOptionPane.OK_CANCEL_OPTION,
1856: JOptionPane.INFORMATION_MESSAGE, null,
1857: null, null);
1858:
1859: if ((option == JOptionPane.CANCEL_OPTION)
1860: || (option == JOptionPane.CLOSED_OPTION)) {
1861: return;
1862: }
1863: }
1864:
1865: String fileStr = file.toString();
1866: file = new File(fileStr);
1867:
1868: HTMLDocument document = (HTMLDocument) transcriptPane
1869: .getDocument();
1870:
1871: try {
1872: String string = document.getText(0, document
1873: .getLength());
1874: FileWriter fileWriter = new FileWriter(file);
1875: fileWriter.write(string, 0, string.length());
1876: fileWriter.flush();
1877: fileWriter.close();
1878: } catch (Exception e) {
1879: e.printStackTrace();
1880: }
1881: }
1882: }
1883: }
1884: }
|